@dopaminefx/core 0.1.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.
Files changed (136) hide show
  1. package/dist/engine/color.d.ts +71 -0
  2. package/dist/engine/color.d.ts.map +1 -0
  3. package/dist/engine/color.js +107 -0
  4. package/dist/engine/color.js.map +1 -0
  5. package/dist/engine/context.d.ts +54 -0
  6. package/dist/engine/context.d.ts.map +1 -0
  7. package/dist/engine/context.js +0 -0
  8. package/dist/engine/context.js.map +1 -0
  9. package/dist/engine/gl.d.ts +9 -0
  10. package/dist/engine/gl.d.ts.map +1 -0
  11. package/dist/engine/gl.js +39 -0
  12. package/dist/engine/gl.js.map +1 -0
  13. package/dist/engine/look/glsl.d.ts +95 -0
  14. package/dist/engine/look/glsl.d.ts.map +1 -0
  15. package/dist/engine/look/glsl.js +171 -0
  16. package/dist/engine/look/glsl.js.map +1 -0
  17. package/dist/engine/look/particles.glsl.d.ts +21 -0
  18. package/dist/engine/look/particles.glsl.d.ts.map +1 -0
  19. package/dist/engine/look/particles.glsl.js +44 -0
  20. package/dist/engine/look/particles.glsl.js.map +1 -0
  21. package/dist/engine/sdf.d.ts +77 -0
  22. package/dist/engine/sdf.d.ts.map +1 -0
  23. package/dist/engine/sdf.js +255 -0
  24. package/dist/engine/sdf.js.map +1 -0
  25. package/dist/engine/seed.d.ts +10 -0
  26. package/dist/engine/seed.d.ts.map +1 -0
  27. package/dist/engine/seed.js +20 -0
  28. package/dist/engine/seed.js.map +1 -0
  29. package/dist/engine/shadow.d.ts +41 -0
  30. package/dist/engine/shadow.d.ts.map +1 -0
  31. package/dist/engine/shadow.js +39 -0
  32. package/dist/engine/shadow.js.map +1 -0
  33. package/dist/engine/tempo.d.ts +33 -0
  34. package/dist/engine/tempo.d.ts.map +1 -0
  35. package/dist/engine/tempo.js +51 -0
  36. package/dist/engine/tempo.js.map +1 -0
  37. package/dist/framework/conductor.d.ts +100 -0
  38. package/dist/framework/conductor.d.ts.map +1 -0
  39. package/dist/framework/conductor.js +493 -0
  40. package/dist/framework/conductor.js.map +1 -0
  41. package/dist/framework/content.d.ts +67 -0
  42. package/dist/framework/content.d.ts.map +1 -0
  43. package/dist/framework/content.js +72 -0
  44. package/dist/framework/content.js.map +1 -0
  45. package/dist/framework/dope-pass.d.ts +131 -0
  46. package/dist/framework/dope-pass.d.ts.map +1 -0
  47. package/dist/framework/dope-pass.js +346 -0
  48. package/dist/framework/dope-pass.js.map +1 -0
  49. package/dist/framework/dope-zip.d.ts +22 -0
  50. package/dist/framework/dope-zip.d.ts.map +1 -0
  51. package/dist/framework/dope-zip.js +116 -0
  52. package/dist/framework/dope-zip.js.map +1 -0
  53. package/dist/framework/effect.d.ts +128 -0
  54. package/dist/framework/effect.d.ts.map +1 -0
  55. package/dist/framework/effect.js +19 -0
  56. package/dist/framework/effect.js.map +1 -0
  57. package/dist/framework/frame-expr.d.ts +124 -0
  58. package/dist/framework/frame-expr.d.ts.map +1 -0
  59. package/dist/framework/frame-expr.js +135 -0
  60. package/dist/framework/frame-expr.js.map +1 -0
  61. package/dist/framework/load-effect.d.ts +77 -0
  62. package/dist/framework/load-effect.d.ts.map +1 -0
  63. package/dist/framework/load-effect.js +135 -0
  64. package/dist/framework/load-effect.js.map +1 -0
  65. package/dist/framework/loader.d.ts +309 -0
  66. package/dist/framework/loader.d.ts.map +1 -0
  67. package/dist/framework/loader.js +266 -0
  68. package/dist/framework/loader.js.map +1 -0
  69. package/dist/framework/mood-registry.d.ts +58 -0
  70. package/dist/framework/mood-registry.d.ts.map +1 -0
  71. package/dist/framework/mood-registry.js +58 -0
  72. package/dist/framework/mood-registry.js.map +1 -0
  73. package/dist/framework/panel-runner.d.ts +96 -0
  74. package/dist/framework/panel-runner.d.ts.map +1 -0
  75. package/dist/framework/panel-runner.js +137 -0
  76. package/dist/framework/panel-runner.js.map +1 -0
  77. package/dist/framework/pass-common.d.ts +97 -0
  78. package/dist/framework/pass-common.d.ts.map +1 -0
  79. package/dist/framework/pass-common.js +178 -0
  80. package/dist/framework/pass-common.js.map +1 -0
  81. package/dist/framework/pass-runner.d.ts +183 -0
  82. package/dist/framework/pass-runner.d.ts.map +1 -0
  83. package/dist/framework/pass-runner.js +212 -0
  84. package/dist/framework/pass-runner.js.map +1 -0
  85. package/dist/framework/programs.d.ts +54 -0
  86. package/dist/framework/programs.d.ts.map +1 -0
  87. package/dist/framework/programs.js +33 -0
  88. package/dist/framework/programs.js.map +1 -0
  89. package/dist/framework/registry.d.ts +29 -0
  90. package/dist/framework/registry.d.ts.map +1 -0
  91. package/dist/framework/registry.js +38 -0
  92. package/dist/framework/registry.js.map +1 -0
  93. package/dist/framework/runtime.d.ts +19 -0
  94. package/dist/framework/runtime.d.ts.map +1 -0
  95. package/dist/framework/runtime.js +37 -0
  96. package/dist/framework/runtime.js.map +1 -0
  97. package/dist/index.d.ts +63 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +126 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/overlay.d.ts +46 -0
  102. package/dist/overlay.d.ts.map +1 -0
  103. package/dist/overlay.js +79 -0
  104. package/dist/overlay.js.map +1 -0
  105. package/dist/types.d.ts +68 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +10 -0
  108. package/dist/types.js.map +1 -0
  109. package/package.json +37 -0
  110. package/src/engine/color.ts +154 -0
  111. package/src/engine/context.ts +0 -0
  112. package/src/engine/gl.ts +46 -0
  113. package/src/engine/look/glsl.ts +183 -0
  114. package/src/engine/look/particles.glsl.ts +44 -0
  115. package/src/engine/sdf.ts +298 -0
  116. package/src/engine/seed.ts +23 -0
  117. package/src/engine/shadow.ts +66 -0
  118. package/src/engine/tempo.ts +54 -0
  119. package/src/framework/conductor.ts +604 -0
  120. package/src/framework/content.ts +113 -0
  121. package/src/framework/dope-pass.ts +432 -0
  122. package/src/framework/dope-zip.ts +125 -0
  123. package/src/framework/effect.ts +127 -0
  124. package/src/framework/frame-expr.ts +217 -0
  125. package/src/framework/load-effect.ts +204 -0
  126. package/src/framework/loader.ts +502 -0
  127. package/src/framework/mood-registry.ts +87 -0
  128. package/src/framework/panel-runner.ts +233 -0
  129. package/src/framework/pass-common.ts +222 -0
  130. package/src/framework/pass-runner.ts +391 -0
  131. package/src/framework/programs.ts +62 -0
  132. package/src/framework/registry.ts +44 -0
  133. package/src/framework/runtime.ts +38 -0
  134. package/src/index.ts +227 -0
  135. package/src/overlay.ts +109 -0
  136. package/src/types.ts +63 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Algorithmic color in OKLCH.
3
+ *
4
+ * OKLCH is perceptually uniform, so walking hue by the golden angle (137.5°)
5
+ * yields palettes that are always harmonious yet never repeat — the novelty
6
+ * that keeps a reward from habituating. Lightness/chroma come from the mood
7
+ * (saturated + bright == higher arousal *and* positive valence).
8
+ *
9
+ * We hand the shader *linear* sRGB, because light should be summed in linear
10
+ * space; sRGB gamma is only for talking to CSS.
11
+ */
12
+ import type { Rng } from "./seed.js";
13
+ /** Linear sRGB, nominally 0..1 (may exceed before clamping). */
14
+ export interface RGB {
15
+ r: number;
16
+ g: number;
17
+ b: number;
18
+ }
19
+ export interface OKLCH {
20
+ /** Perceptual lightness, 0..1. */
21
+ L: number;
22
+ /** Chroma (colorfulness), ~0..0.4. */
23
+ C: number;
24
+ /** Hue in degrees, 0..360. */
25
+ h: number;
26
+ }
27
+ export declare const GOLDEN_ANGLE_DEG = 137.50776405003785;
28
+ /** Positive modulo into [0, 360). */
29
+ export declare const wrapHue: (h: number) => number;
30
+ /**
31
+ * OKLCH → linear sRGB (Björn Ottosson's OKLab matrices). Result is gamut-clamped
32
+ * to [0, 1] per channel.
33
+ */
34
+ export declare function oklchToLinearSrgb({ L, C, h }: OKLCH): RGB;
35
+ export interface PaletteParams {
36
+ /** Base lightness for the stops. */
37
+ lightness: number;
38
+ /** Base chroma for the stops. */
39
+ chroma: number;
40
+ /** Center of the hue range this mood prefers, in degrees. */
41
+ hueCenter: number;
42
+ /** Width of the random hue range around the center, in degrees. */
43
+ hueRange: number;
44
+ /** 0..1 — how far the golden-angle stops fan out from the base hue. */
45
+ hueSpread: number;
46
+ }
47
+ /**
48
+ * Build a 3-stop linear-RGB palette. The base hue is drawn from `rng` (so an
49
+ * un-pinned seed gives a unique palette each fire), biased toward the mood's
50
+ * preferred range. Successive stops step by the golden angle, scaled by whimsy.
51
+ * Lightness and chroma breathe slightly across the stops for depth.
52
+ */
53
+ export declare function buildPalette(rng: Rng, p: PaletteParams): RGB[];
54
+ /** A parsed backdrop colour the overlay composites against. */
55
+ export interface Backdrop {
56
+ /** sRGB 0..1 (the value as authored — NOT linearised). */
57
+ rgb: RGB;
58
+ /** Rec.709 relative luminance in sRGB space, 0 (black) .. 1 (white). */
59
+ luminance: number;
60
+ }
61
+ /**
62
+ * Parse any CSS colour string into a {@link Backdrop} (sRGB 0..1 + luminance),
63
+ * or `null` if it can't be understood. Handles hex and `rgb()/rgba()` directly;
64
+ * for everything else (named colours, `hsl()`, `color()`) it falls back to the
65
+ * browser's own normaliser via a throwaway 2D canvas when a DOM is present. The
66
+ * runtime uses the luminance to decide how strongly to cast shadow as a surface
67
+ * lightens; the colour itself isn't sent to the shader (the light layer
68
+ * composites source-over against the live page).
69
+ */
70
+ export declare function parseBackdrop(css: string): Backdrop | null;
71
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/engine/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,gEAAgE;AAChE,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,CAAC,EAAE,MAAM,CAAC;IACV,sCAAsC;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,8BAA8B;IAC9B,CAAC,EAAE,MAAM,CAAC;CACX;AAED,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAInD,qCAAqC;AACrC,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,KAAG,MAAiC,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,GAAG,GAAG,CAkBzD;AAED,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,aAAa,GAAG,GAAG,EAAE,CAa9D;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,0DAA0D;IAC1D,GAAG,EAAE,GAAG,CAAC;IACT,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;CACnB;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgB1D"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Algorithmic color in OKLCH.
3
+ *
4
+ * OKLCH is perceptually uniform, so walking hue by the golden angle (137.5°)
5
+ * yields palettes that are always harmonious yet never repeat — the novelty
6
+ * that keeps a reward from habituating. Lightness/chroma come from the mood
7
+ * (saturated + bright == higher arousal *and* positive valence).
8
+ *
9
+ * We hand the shader *linear* sRGB, because light should be summed in linear
10
+ * space; sRGB gamma is only for talking to CSS.
11
+ */
12
+ export const GOLDEN_ANGLE_DEG = 137.50776405003785;
13
+ const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
14
+ /** Positive modulo into [0, 360). */
15
+ export const wrapHue = (h) => ((h % 360) + 360) % 360;
16
+ /**
17
+ * OKLCH → linear sRGB (Björn Ottosson's OKLab matrices). Result is gamut-clamped
18
+ * to [0, 1] per channel.
19
+ */
20
+ export function oklchToLinearSrgb({ L, C, h }) {
21
+ const hr = (h * Math.PI) / 180;
22
+ const a = C * Math.cos(hr);
23
+ const b = C * Math.sin(hr);
24
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
25
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
26
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
27
+ const l = l_ * l_ * l_;
28
+ const m = m_ * m_ * m_;
29
+ const s = s_ * s_ * s_;
30
+ return {
31
+ r: clamp01(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
32
+ g: clamp01(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
33
+ b: clamp01(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
34
+ };
35
+ }
36
+ /**
37
+ * Build a 3-stop linear-RGB palette. The base hue is drawn from `rng` (so an
38
+ * un-pinned seed gives a unique palette each fire), biased toward the mood's
39
+ * preferred range. Successive stops step by the golden angle, scaled by whimsy.
40
+ * Lightness and chroma breathe slightly across the stops for depth.
41
+ */
42
+ export function buildPalette(rng, p) {
43
+ const baseHue = wrapHue(p.hueCenter + (rng() - 0.5) * p.hueRange);
44
+ const step = GOLDEN_ANGLE_DEG * (0.35 + 0.65 * p.hueSpread);
45
+ const lightSteps = [0.0, 0.06, -0.05];
46
+ const chromaSteps = [0.0, 0.02, -0.01];
47
+ return [0, 1, 2].map((i) => oklchToLinearSrgb({
48
+ L: clamp01(p.lightness + lightSteps[i]),
49
+ C: Math.max(0, p.chroma + chromaSteps[i]),
50
+ h: wrapHue(baseHue + step * i),
51
+ }));
52
+ }
53
+ /** Parse `#rgb[a]` / `#rrggbb[aa]` hex (3/4/6/8 digits) into sRGB 0..1, or null. */
54
+ function parseHex(s) {
55
+ const m = /^#([0-9a-f]{3,8})$/i.exec(s);
56
+ if (!m)
57
+ return null;
58
+ const h = m[1];
59
+ const dup = (c) => parseInt(c + c, 16) / 255;
60
+ if (h.length === 3 || h.length === 4)
61
+ return { r: dup(h[0]), g: dup(h[1]), b: dup(h[2]) };
62
+ if (h.length === 6 || h.length === 8) {
63
+ const at = (i) => parseInt(h.slice(i, i + 2), 16) / 255;
64
+ return { r: at(0), g: at(2), b: at(4) };
65
+ }
66
+ return null;
67
+ }
68
+ /** Parse `rgb()/rgba()` (comma- or space-separated, 0–255 or %), or null. */
69
+ function parseRgbFunc(s) {
70
+ const m = /^rgba?\(([^)]+)\)$/i.exec(s.trim());
71
+ if (!m)
72
+ return null;
73
+ const parts = m[1].split(/[\s,/]+/).filter(Boolean).slice(0, 3);
74
+ if (parts.length < 3)
75
+ return null;
76
+ const chan = (p) => p.endsWith("%") ? clamp01(parseFloat(p) / 100) : clamp01(parseFloat(p) / 255);
77
+ return { r: chan(parts[0]), g: chan(parts[1]), b: chan(parts[2]) };
78
+ }
79
+ /**
80
+ * Parse any CSS colour string into a {@link Backdrop} (sRGB 0..1 + luminance),
81
+ * or `null` if it can't be understood. Handles hex and `rgb()/rgba()` directly;
82
+ * for everything else (named colours, `hsl()`, `color()`) it falls back to the
83
+ * browser's own normaliser via a throwaway 2D canvas when a DOM is present. The
84
+ * runtime uses the luminance to decide how strongly to cast shadow as a surface
85
+ * lightens; the colour itself isn't sent to the shader (the light layer
86
+ * composites source-over against the live page).
87
+ */
88
+ export function parseBackdrop(css) {
89
+ let rgb = parseHex(css) ?? parseRgbFunc(css);
90
+ if (!rgb && typeof document !== "undefined") {
91
+ try {
92
+ const ctx = document.createElement("canvas").getContext("2d");
93
+ if (ctx) {
94
+ ctx.fillStyle = "#000";
95
+ ctx.fillStyle = css; // the browser normalises to #rrggbb or rgb(a)(...)
96
+ rgb = parseHex(ctx.fillStyle) ?? parseRgbFunc(ctx.fillStyle);
97
+ }
98
+ }
99
+ catch {
100
+ /* no canvas — fall through to null */
101
+ }
102
+ }
103
+ if (!rgb)
104
+ return null;
105
+ return { rgb, luminance: 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b };
106
+ }
107
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/engine/color.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Shared WebGL2 context + program cache.
3
+ *
4
+ * Before: every fire created a fresh canvas, a fresh WebGL context, and *linked
5
+ * a fresh program* (compiling both shaders) — once for the light pass and again
6
+ * for the shadow pass. Shader compilation is the single most expensive thing we
7
+ * do (tens of ms under SwiftShader), so doing it per fire made rapid/concurrent
8
+ * celebrations stutter and leaked GL contexts (browsers cap live contexts at
9
+ * ~16, after which the oldest is silently lost).
10
+ *
11
+ * After: a `GLContext` wraps one WebGL2 context and caches programs by shader
12
+ * source, so a given effect's shader links *once* for the lifetime of that
13
+ * context and every subsequent fire reuses it. Uniform locations are cached on
14
+ * the program. The conductor keeps one light context + one shadow context alive
15
+ * per target for the page's lifetime, so the expensive link happens once.
16
+ *
17
+ * The context is an abstraction (`GLContext`) rather than a raw `gl` so a future
18
+ * WebGPU backend can implement the same surface without touching effect code.
19
+ */
20
+ /** A linked program with its uniform locations pre-resolved + cached. */
21
+ export interface CachedProgram {
22
+ readonly program: WebGLProgram;
23
+ /** Resolve (and memoize) a uniform location by name. */
24
+ uniform(name: string): WebGLUniformLocation | null;
25
+ /** Resolve a whole set of uniforms at once (memoized). */
26
+ uniforms<N extends string>(names: readonly N[]): Record<N, WebGLUniformLocation | null>;
27
+ }
28
+ export interface GLContext {
29
+ readonly gl: WebGL2RenderingContext;
30
+ readonly canvas: HTMLCanvasElement;
31
+ /**
32
+ * Get a cached program for the given vertex+fragment source, linking it once
33
+ * on first request and reusing it thereafter.
34
+ */
35
+ program(vertexSrc: string, fragmentSrc: string): CachedProgram;
36
+ /** A shared empty VAO (the effects draw a full-screen triangle, no buffers). */
37
+ readonly vao: WebGLVertexArrayObject;
38
+ /** Tear everything down — programs, VAO, context. Used on full teardown. */
39
+ destroy(): void;
40
+ }
41
+ /** Options controlling how the drawing buffer composites with the page. */
42
+ export interface GLContextOptions {
43
+ /**
44
+ * Give the canvas a PREMULTIPLIED-alpha drawing buffer (`alpha: true`,
45
+ * `premultipliedAlpha: true`). The default (false) is the historical opaque
46
+ * buffer the `mix-blend-mode: screen` light layer relies on. The
47
+ * backdrop-aware compositing path turns this on so the light layer can emit
48
+ * premultiplied light and composite source-over — visible on any surface.
49
+ */
50
+ premultiplied?: boolean;
51
+ }
52
+ /** Build a `GLContext` backed by `canvas`. Throws if WebGL2 is unavailable. */
53
+ export declare function createGLContext(canvas: HTMLCanvasElement, opts?: GLContextOptions): GLContext;
54
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/engine/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,wDAAwD;IACxD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAAC;IACnD,0DAA0D;IAC1D,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC,CAAC;CACzF;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,sBAAsB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/D,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,sBAAsB,CAAC;IACrC,4EAA4E;IAC5E,OAAO,IAAI,IAAI,CAAC;CACjB;AASD,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,iBAAiB,EACzB,IAAI,GAAE,gBAAqB,GAC1B,SAAS,CAoDX"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/engine/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAyBtC,MAAM,OAAO,GAA2B;IACtC,KAAK,EAAE,KAAK;IACZ,SAAS,EAAE,KAAK;IAChB,kBAAkB,EAAE,KAAK;IACzB,eAAe,EAAE,kBAAkB;CACpC,CAAC;AAcF,+EAA+E;AAC/E,MAAM,UAAU,eAAe,CAC7B,MAAyB,EACzB,OAAyB,EAAE;IAE3B,MAAM,KAAK,GAA2B,IAAI,CAAC,aAAa;QACtD,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE;QACvD,CAAC,CAAC,OAAO,CAAC;IACZ,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAE9D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,MAAM,GAAG,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;IACnC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAE5D,MAAM,UAAU,GAAG,CAAC,OAAqB,EAAiB,EAAE;QAC1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuC,CAAC;QAC5D,MAAM,OAAO,GAAG,CAAC,IAAY,EAA+B,EAAE;YAC5D,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,GAAG,GAAG,EAAE,CAAC,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC3C,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACtB,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QACF,OAAO;YACL,OAAO;YACP,OAAO;YACP,QAAQ,CAAmB,KAAmB;gBAC5C,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAG1D,CAAC;YACJ,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO;QACL,EAAE;QACF,MAAM;QACN,GAAG;QACH,OAAO,CAAC,SAAiB,EAAE,WAAmB;YAC5C,MAAM,GAAG,GAAG,GAAG,SAAS,IAAI,WAAW,EAAE,CAAC;YAC1C,IAAI,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;gBAC7D,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC5B,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO;YACL,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,QAAQ,CAAC,MAAM,EAAE;gBAAE,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YACvE,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Low-level WebGL2 helpers: shader compilation + program linking. Kept separate
3
+ * from any particular effect so every effect (and the shared context's program
4
+ * cache) reuses the exact same, well-tested path. This is the single place a
5
+ * GLSL program is compiled in the whole library.
6
+ */
7
+ /** Compile + link a vertex/fragment pair into a program. */
8
+ export declare function linkProgram(gl: WebGL2RenderingContext, vertexSrc: string, fragmentSrc: string): WebGLProgram;
9
+ //# sourceMappingURL=gl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gl.d.ts","sourceRoot":"","sources":["../../src/engine/gl.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH,4DAA4D;AAC5D,wBAAgB,WAAW,CACzB,EAAE,EAAE,sBAAsB,EAC1B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,YAAY,CAgBd"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Low-level WebGL2 helpers: shader compilation + program linking. Kept separate
3
+ * from any particular effect so every effect (and the shared context's program
4
+ * cache) reuses the exact same, well-tested path. This is the single place a
5
+ * GLSL program is compiled in the whole library.
6
+ */
7
+ function compileShader(gl, type, src) {
8
+ const sh = gl.createShader(type);
9
+ if (!sh)
10
+ throw new Error("dopamine: failed to create shader");
11
+ gl.shaderSource(sh, src);
12
+ gl.compileShader(sh);
13
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
14
+ const log = gl.getShaderInfoLog(sh);
15
+ gl.deleteShader(sh);
16
+ throw new Error(`dopamine: shader compile error\n${log ?? ""}`);
17
+ }
18
+ return sh;
19
+ }
20
+ /** Compile + link a vertex/fragment pair into a program. */
21
+ export function linkProgram(gl, vertexSrc, fragmentSrc) {
22
+ const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);
23
+ const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);
24
+ const program = gl.createProgram();
25
+ if (!program)
26
+ throw new Error("dopamine: failed to create program");
27
+ gl.attachShader(program, vs);
28
+ gl.attachShader(program, fs);
29
+ gl.linkProgram(program);
30
+ gl.deleteShader(vs);
31
+ gl.deleteShader(fs);
32
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
33
+ const log = gl.getProgramInfoLog(program);
34
+ gl.deleteProgram(program);
35
+ throw new Error(`dopamine: program link error\n${log ?? ""}`);
36
+ }
37
+ return program;
38
+ }
39
+ //# sourceMappingURL=gl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gl.js","sourceRoot":"","sources":["../../src/engine/gl.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,SAAS,aAAa,CACpB,EAA0B,EAC1B,IAAY,EACZ,GAAW;IAEX,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC9D,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACzB,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACrB,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACpC,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,WAAW,CACzB,EAA0B,EAC1B,SAAiB,EACjB,WAAmB;IAEnB,MAAM,EAAE,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAC1D,MAAM,EAAE,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC;IACnC,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC7B,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC7B,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACxB,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACpB,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACpB,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC1C,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared GLSL "look" chunk library.
3
+ *
4
+ * The three effects grew in parallel and each re-implemented (and drifted) the
5
+ * same building blocks inside its own shader: value-noise + fbm + domain warp,
6
+ * the palette mix, the segment SDF, the ACES tonemap, the IQ-cosine iridescence,
7
+ * the Ben-Day halftone and the ordered dither. Per the cross-pollination plan,
8
+ * those are lifted here into ONE canonical copy each, so every shader composes
9
+ * the SAME function instead of a private fork. Effects assemble their fragment
10
+ * source by concatenating the chunks they need ahead of their own `main()`.
11
+ *
12
+ * Each chunk is a self-contained GLSL snippet (no `#version`/`precision`/IO —
13
+ * those stay in the per-effect shader). The text below is byte-identical to the
14
+ * canonical implementations the effects already shipped, so adopting the library
15
+ * does not change any effect's look; it only removes the duplication + drift.
16
+ *
17
+ * NOTE: This is a GLSL *chunk* library (string includes), not a transpiler — it
18
+ * maps onto the `.dope` format's referenced shader bodies (the format references
19
+ * GLSL; it does not generate it).
20
+ */
21
+ /** TAU + a couple of constants every effect uses. */
22
+ export declare const GLSL_CONSTANTS = "\n#define TAU 6.28318530718\n";
23
+ /**
24
+ * Hash helpers (Dave Hoskins style) — `hash11` (1→1) and `hash21` (1→2). Used
25
+ * by the noise field, the particles, and the per-frame dither.
26
+ */
27
+ export declare const GLSL_HASH = "\nfloat hash11(float p){ p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }\nvec2 hash21(float p){\n vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n";
28
+ /**
29
+ * Value noise + 4-octave fbm with a per-octave rotation (kills axis-aligned
30
+ * artifacts). Requires GLSL_HASH. This is the volumetric texture source for the
31
+ * bloom interior and the wet-ink edge wobble.
32
+ */
33
+ export declare const GLSL_FBM = "\nfloat vnoise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n float a = hash11(dot(i, vec2(1.0, 57.0)));\n float b = hash11(dot(i + vec2(1.0, 0.0), vec2(1.0, 57.0)));\n float c = hash11(dot(i + vec2(0.0, 1.0), vec2(1.0, 57.0)));\n float d = hash11(dot(i + vec2(1.0, 1.0), vec2(1.0, 57.0)));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\nfloat fbm(vec2 p){\n float s = 0.0, a = 0.5;\n mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);\n for (int i = 0; i < 4; i++) { s += a * vnoise(p); p = rot * p * 2.03; a *= 0.5; }\n return s;\n}\n";
34
+ /**
35
+ * Two-level domain warp: warp the sample point by an fbm-derived offset, then
36
+ * sample fbm again — the smoke-like living interior of a real bloom. Returns the
37
+ * warped fbm value at `p`; `t` is time, `amount` the warp strength. Requires
38
+ * GLSL_FBM.
39
+ */
40
+ export declare const GLSL_DOMAIN_WARP = "\nfloat domainWarp(vec2 p, float t, float amount){\n vec2 warp = vec2(fbm(p + t * 0.18), fbm(p.yx - t * 0.12)) - 0.5;\n return fbm(p + warp * 1.2 * amount + t * 0.25);\n}\n";
41
+ /** 3-stop palette mix (inner→mid→outer). Effects declare uC0/uC1/uC2 uniforms. */
42
+ export declare const GLSL_PALETTE_MIX = "\nvec3 paletteMix(float t){\n t = clamp(t, 0.0, 1.0);\n return t < 0.5 ? mix(uC0, uC1, t * 2.0) : mix(uC1, uC2, (t - 0.5) * 2.0);\n}\n";
43
+ /**
44
+ * Inigo Quilez cosine palette — a smooth spectral sweep used for thin-film
45
+ * iridescence (oil-on-water sheen): cycles through complementary hues, NOT the
46
+ * mood palette, so it reads as an iridescent film over the mark.
47
+ */
48
+ export declare const GLSL_IRIDESCENT = "\nvec3 iridescent(float t){\n return 0.55 + 0.45 * cos(TAU * (vec3(1.0) * t + vec3(0.0, 0.33, 0.67)));\n}\n";
49
+ /**
50
+ * Spectral dispersion amount at a refractive edge — grows toward the rim and
51
+ * with amplitude, gated by a 0..1 strength. `dn` is normalized radius/edge
52
+ * proximity (1 == edge); used to sample a profile at channel-shifted positions.
53
+ */
54
+ export declare const GLSL_DISPERSION = "\nfloat dispersionAmount(float strength, float dn, float amp){\n return strength * (0.06 + 0.12 * smoothstep(0.2, 1.1, dn)) * (0.7 + 0.5 * amp);\n}\n";
55
+ /** Capsule/segment SDF (the safe variant — guards zero-length segments). */
56
+ export declare const GLSL_SD_SEG = "\nfloat sdSeg(vec2 p, vec2 a, vec2 b){\n vec2 pa = p - a, ba = b - a;\n float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-3), 0.0, 1.0);\n return length(pa - ba * h);\n}\n";
57
+ /**
58
+ * ACES filmic tonemap (Narkowicz) — richer highlight rolloff than `x/(1+x)`,
59
+ * keeps highlights from going chalky while preserving saturated mid-lights.
60
+ */
61
+ export declare const GLSL_TONEMAP_ACES = "\nvec3 tonemapACES(vec3 x){\n const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n";
62
+ /**
63
+ * Ordered/triangular-hash dither, ~1/255, to break the smooth-gradient banding
64
+ * the screen blend reveals on the page beneath. `frag` device px, `t` seconds,
65
+ * `fade` 0..1 (1 = full dither; effects fade it out toward the cel/pop end where
66
+ * hard bands are intended). Requires GLSL_HASH.
67
+ */
68
+ export declare const GLSL_DITHER = "\nvec3 ditherAdd(vec3 col, vec2 frag, float t, float fade){\n float dz = hash11(dot(frag, vec2(12.989, 78.233)) + t) - 0.5;\n return col + (dz / 255.0) * fade;\n}\n";
69
+ /**
70
+ * Ben-Day halftone coverage: 1 inside a dot, 0 outside, antialiased. Dot RADIUS
71
+ * grows with tone `v`; the screen is rotated by `ang` (classic per-channel
72
+ * screen angle). Requires the matrix helper below.
73
+ */
74
+ export declare const GLSL_ROT2 = "\nmat2 rot2(float a){ float s = sin(a), c = cos(a); return mat2(c, -s, s, c); }\n";
75
+ export declare const GLSL_HALFTONE = "\nfloat benday(vec2 frag, float cell, float v, float ang){\n vec2 p = rot2(ang) * frag / cell;\n vec2 g = fract(p) - 0.5;\n float d = length(g);\n float r = 0.52 * sqrt(clamp(v, 0.0, 1.0));\n float aa = 0.7 / cell + fwidth(d);\n return 1.0 - smoothstep(r - aa, r + aa, d);\n}\n";
76
+ /**
77
+ * PREMULTIPLIED LIGHT OUT — the portable compositing emit.
78
+ *
79
+ * The default web overlay leans on CSS `mix-blend-mode: screen` to cast the
80
+ * effect's light onto the page, which is rich on a dark UI but mathematically
81
+ * invisible on white (`screen(x, 1) == 1`). The native stacks (iOS/Android) have
82
+ * no per-surface screen blend, so they instead emit PREMULTIPLIED light — `rgb`
83
+ * unchanged, `alpha = its own brightness` — and let the OS composite it
84
+ * source-over: dark regions go transparent (the host shows through), bright
85
+ * light reads as cast colour, and crucially it stays visible on ANY backdrop,
86
+ * white included. This is byte-identical to Android's `Look.kt` `dopLightOut`.
87
+ *
88
+ * The web runtime reuses this for its "backdrop"-aware compositing path: when a
89
+ * caller passes a known surface colour, the light pass swaps its opaque
90
+ * `vec4(col, 1.0)` emit for `dopLightOut(col)` and composites source-over
91
+ * (`mix-blend-mode: normal`) instead of screen — see `pass-common.ts`'s
92
+ * `compositeLightFragment`.
93
+ */
94
+ export declare const GLSL_LIGHT_OUT = "\nvec4 dopLightOut(vec3 col){\n col = max(col, 0.0);\n float a = clamp(max(max(col.r, col.g), col.b), 0.0, 1.0);\n return vec4(col, a);\n}\n";
95
+ //# sourceMappingURL=glsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glsl.d.ts","sourceRoot":"","sources":["../../../src/engine/look/glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qDAAqD;AACrD,eAAO,MAAM,cAAc,kCAE1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,uQAOrB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,0kBAgBpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,mLAK5B,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,6IAK5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,iHAI3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,2JAI3B,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,WAAW,mLAMvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,4KAK7B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,2KAKvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,sFAErB,CAAC;AAEF,eAAO,MAAM,aAAa,gSASzB,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,oJAM1B,CAAC"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Shared GLSL "look" chunk library.
3
+ *
4
+ * The three effects grew in parallel and each re-implemented (and drifted) the
5
+ * same building blocks inside its own shader: value-noise + fbm + domain warp,
6
+ * the palette mix, the segment SDF, the ACES tonemap, the IQ-cosine iridescence,
7
+ * the Ben-Day halftone and the ordered dither. Per the cross-pollination plan,
8
+ * those are lifted here into ONE canonical copy each, so every shader composes
9
+ * the SAME function instead of a private fork. Effects assemble their fragment
10
+ * source by concatenating the chunks they need ahead of their own `main()`.
11
+ *
12
+ * Each chunk is a self-contained GLSL snippet (no `#version`/`precision`/IO —
13
+ * those stay in the per-effect shader). The text below is byte-identical to the
14
+ * canonical implementations the effects already shipped, so adopting the library
15
+ * does not change any effect's look; it only removes the duplication + drift.
16
+ *
17
+ * NOTE: This is a GLSL *chunk* library (string includes), not a transpiler — it
18
+ * maps onto the `.dope` format's referenced shader bodies (the format references
19
+ * GLSL; it does not generate it).
20
+ */
21
+ /** TAU + a couple of constants every effect uses. */
22
+ export const GLSL_CONSTANTS = /* glsl */ `
23
+ #define TAU 6.28318530718
24
+ `;
25
+ /**
26
+ * Hash helpers (Dave Hoskins style) — `hash11` (1→1) and `hash21` (1→2). Used
27
+ * by the noise field, the particles, and the per-frame dither.
28
+ */
29
+ export const GLSL_HASH = /* glsl */ `
30
+ float hash11(float p){ p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }
31
+ vec2 hash21(float p){
32
+ vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));
33
+ p3 += dot(p3, p3.yzx + 33.33);
34
+ return fract((p3.xx + p3.yz) * p3.zy);
35
+ }
36
+ `;
37
+ /**
38
+ * Value noise + 4-octave fbm with a per-octave rotation (kills axis-aligned
39
+ * artifacts). Requires GLSL_HASH. This is the volumetric texture source for the
40
+ * bloom interior and the wet-ink edge wobble.
41
+ */
42
+ export const GLSL_FBM = /* glsl */ `
43
+ float vnoise(vec2 p){
44
+ vec2 i = floor(p), f = fract(p);
45
+ vec2 u = f * f * (3.0 - 2.0 * f);
46
+ float a = hash11(dot(i, vec2(1.0, 57.0)));
47
+ float b = hash11(dot(i + vec2(1.0, 0.0), vec2(1.0, 57.0)));
48
+ float c = hash11(dot(i + vec2(0.0, 1.0), vec2(1.0, 57.0)));
49
+ float d = hash11(dot(i + vec2(1.0, 1.0), vec2(1.0, 57.0)));
50
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
51
+ }
52
+ float fbm(vec2 p){
53
+ float s = 0.0, a = 0.5;
54
+ mat2 rot = mat2(0.80, -0.60, 0.60, 0.80);
55
+ for (int i = 0; i < 4; i++) { s += a * vnoise(p); p = rot * p * 2.03; a *= 0.5; }
56
+ return s;
57
+ }
58
+ `;
59
+ /**
60
+ * Two-level domain warp: warp the sample point by an fbm-derived offset, then
61
+ * sample fbm again — the smoke-like living interior of a real bloom. Returns the
62
+ * warped fbm value at `p`; `t` is time, `amount` the warp strength. Requires
63
+ * GLSL_FBM.
64
+ */
65
+ export const GLSL_DOMAIN_WARP = /* glsl */ `
66
+ float domainWarp(vec2 p, float t, float amount){
67
+ vec2 warp = vec2(fbm(p + t * 0.18), fbm(p.yx - t * 0.12)) - 0.5;
68
+ return fbm(p + warp * 1.2 * amount + t * 0.25);
69
+ }
70
+ `;
71
+ /** 3-stop palette mix (inner→mid→outer). Effects declare uC0/uC1/uC2 uniforms. */
72
+ export const GLSL_PALETTE_MIX = /* glsl */ `
73
+ vec3 paletteMix(float t){
74
+ t = clamp(t, 0.0, 1.0);
75
+ return t < 0.5 ? mix(uC0, uC1, t * 2.0) : mix(uC1, uC2, (t - 0.5) * 2.0);
76
+ }
77
+ `;
78
+ /**
79
+ * Inigo Quilez cosine palette — a smooth spectral sweep used for thin-film
80
+ * iridescence (oil-on-water sheen): cycles through complementary hues, NOT the
81
+ * mood palette, so it reads as an iridescent film over the mark.
82
+ */
83
+ export const GLSL_IRIDESCENT = /* glsl */ `
84
+ vec3 iridescent(float t){
85
+ return 0.55 + 0.45 * cos(TAU * (vec3(1.0) * t + vec3(0.0, 0.33, 0.67)));
86
+ }
87
+ `;
88
+ /**
89
+ * Spectral dispersion amount at a refractive edge — grows toward the rim and
90
+ * with amplitude, gated by a 0..1 strength. `dn` is normalized radius/edge
91
+ * proximity (1 == edge); used to sample a profile at channel-shifted positions.
92
+ */
93
+ export const GLSL_DISPERSION = /* glsl */ `
94
+ float dispersionAmount(float strength, float dn, float amp){
95
+ return strength * (0.06 + 0.12 * smoothstep(0.2, 1.1, dn)) * (0.7 + 0.5 * amp);
96
+ }
97
+ `;
98
+ /** Capsule/segment SDF (the safe variant — guards zero-length segments). */
99
+ export const GLSL_SD_SEG = /* glsl */ `
100
+ float sdSeg(vec2 p, vec2 a, vec2 b){
101
+ vec2 pa = p - a, ba = b - a;
102
+ float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-3), 0.0, 1.0);
103
+ return length(pa - ba * h);
104
+ }
105
+ `;
106
+ /**
107
+ * ACES filmic tonemap (Narkowicz) — richer highlight rolloff than `x/(1+x)`,
108
+ * keeps highlights from going chalky while preserving saturated mid-lights.
109
+ */
110
+ export const GLSL_TONEMAP_ACES = /* glsl */ `
111
+ vec3 tonemapACES(vec3 x){
112
+ const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
113
+ return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
114
+ }
115
+ `;
116
+ /**
117
+ * Ordered/triangular-hash dither, ~1/255, to break the smooth-gradient banding
118
+ * the screen blend reveals on the page beneath. `frag` device px, `t` seconds,
119
+ * `fade` 0..1 (1 = full dither; effects fade it out toward the cel/pop end where
120
+ * hard bands are intended). Requires GLSL_HASH.
121
+ */
122
+ export const GLSL_DITHER = /* glsl */ `
123
+ vec3 ditherAdd(vec3 col, vec2 frag, float t, float fade){
124
+ float dz = hash11(dot(frag, vec2(12.989, 78.233)) + t) - 0.5;
125
+ return col + (dz / 255.0) * fade;
126
+ }
127
+ `;
128
+ /**
129
+ * Ben-Day halftone coverage: 1 inside a dot, 0 outside, antialiased. Dot RADIUS
130
+ * grows with tone `v`; the screen is rotated by `ang` (classic per-channel
131
+ * screen angle). Requires the matrix helper below.
132
+ */
133
+ export const GLSL_ROT2 = /* glsl */ `
134
+ mat2 rot2(float a){ float s = sin(a), c = cos(a); return mat2(c, -s, s, c); }
135
+ `;
136
+ export const GLSL_HALFTONE = /* glsl */ `
137
+ float benday(vec2 frag, float cell, float v, float ang){
138
+ vec2 p = rot2(ang) * frag / cell;
139
+ vec2 g = fract(p) - 0.5;
140
+ float d = length(g);
141
+ float r = 0.52 * sqrt(clamp(v, 0.0, 1.0));
142
+ float aa = 0.7 / cell + fwidth(d);
143
+ return 1.0 - smoothstep(r - aa, r + aa, d);
144
+ }
145
+ `;
146
+ /**
147
+ * PREMULTIPLIED LIGHT OUT — the portable compositing emit.
148
+ *
149
+ * The default web overlay leans on CSS `mix-blend-mode: screen` to cast the
150
+ * effect's light onto the page, which is rich on a dark UI but mathematically
151
+ * invisible on white (`screen(x, 1) == 1`). The native stacks (iOS/Android) have
152
+ * no per-surface screen blend, so they instead emit PREMULTIPLIED light — `rgb`
153
+ * unchanged, `alpha = its own brightness` — and let the OS composite it
154
+ * source-over: dark regions go transparent (the host shows through), bright
155
+ * light reads as cast colour, and crucially it stays visible on ANY backdrop,
156
+ * white included. This is byte-identical to Android's `Look.kt` `dopLightOut`.
157
+ *
158
+ * The web runtime reuses this for its "backdrop"-aware compositing path: when a
159
+ * caller passes a known surface colour, the light pass swaps its opaque
160
+ * `vec4(col, 1.0)` emit for `dopLightOut(col)` and composites source-over
161
+ * (`mix-blend-mode: normal`) instead of screen — see `pass-common.ts`'s
162
+ * `compositeLightFragment`.
163
+ */
164
+ export const GLSL_LIGHT_OUT = /* glsl */ `
165
+ vec4 dopLightOut(vec3 col){
166
+ col = max(col, 0.0);
167
+ float a = clamp(max(max(col.r, col.g), col.b), 0.0, 1.0);
168
+ return vec4(col, a);
169
+ }
170
+ `;
171
+ //# sourceMappingURL=glsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glsl.js","sourceRoot":"","sources":["../../../src/engine/look/glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;;CAExC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC;;;;;;;CAOnC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;CAgBlC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;CAK1C,CAAC;AAEF,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;CAK1C,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;;;;CAIzC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;;;;CAIzC,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;;;;;;CAMrC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC;;;;;CAK3C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;;;;;CAKrC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC;;CAEnC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;;;;;;;;;CASvC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;;;;;;CAMxC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared GPU-particle GLSL chunk.
3
+ *
4
+ * Solarbloom's drifting "motes" and Verdict's flung "droplets" are both
5
+ * point-sprite particle fields that differ only in their MOTION model (motes:
6
+ * outward drift + buoyancy + curl, with motion-blur streaks; droplets: a
7
+ * ballistic arc under gravity) and their styling. The cross-pollination plan
8
+ * asks for one parametric particle module behind a shared include + params.
9
+ *
10
+ * Here we extract the parts that were duplicated verbatim between the two: the
11
+ * per-particle soft round sprite (`particleSprite`), the ballistic position
12
+ * (`ballisticPos`), and the standard fade-in/out over a particle's life
13
+ * (`particleFade`). Each effect keeps its own emit shape + motion (the part that
14
+ * is its identity) and composes these shared primitives, so the dot falloff,
15
+ * the gravity arc and the lifetime curve no longer drift between effects.
16
+ *
17
+ * Comic debris can adopt the same primitives later (deferred — noted in the
18
+ * plan as P2). Requires no other chunk.
19
+ */
20
+ export declare const GLSL_PARTICLES = "\n// Soft round particle sprite: an inverse-distance dot that, squared, gives the\n// glowing-photon falloff both motes and droplets use. `d` is distance to the\n// particle centre, `size` its radius in device px.\nfloat particleSprite(float d, float size){\n float s = size / (d + size * 0.5);\n return s * s;\n}\n\n// Ballistic arc: launch from `origin` along `dir` at `speed`, pulled down by\n// `gravity` (device px) over normalized particle life `t` (0..1). Screen y is\n// up, so gravity subtracts on y. Used by Verdict's flung ink droplets and any\n// effect that wants sparks thrown off an impact.\nvec2 ballisticPos(vec2 origin, vec2 dir, float speed, float gravity, float t){\n return origin + dir * speed * t - vec2(0.0, 1.0) * gravity * t * t;\n}\n\n// Standard particle fade: ramps in fast then fades out across its life. `t` is\n// normalized particle life (0..1); `tailPow` shapes the decay (higher = longer\n// luminous body before it dims).\nfloat particleFade(float t, float tailPow){\n return (1.0 - pow(t, tailPow)) * smoothstep(0.0, 0.08, t);\n}\n";
21
+ //# sourceMappingURL=particles.glsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"particles.glsl.d.ts","sourceRoot":"","sources":["../../../src/engine/look/particles.glsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,cAAc,qjCAuB1B,CAAC"}