@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,22 @@
1
+ /**
2
+ * Minimal `.dope` zip (dotLottie-style) reader.
3
+ *
4
+ * A distributed `.dope` may be a zip: a `manifest.json` naming the effect doc
5
+ * plus an `assets/` dir referenced by RELATIVE paths only. This reads the zip
6
+ * entries (STORED or DEFLATE), finds the effect JSON via the manifest (or the
7
+ * first `*.json` under `effects/`), and resolves any relative asset `$ref`s in
8
+ * `geometry.outlines.*` by inlining them as `data:` URIs — so the doc handed to
9
+ * the loader is fully self-contained. Remote/absolute refs are rejected (the
10
+ * loader's standalone guard would reject them anyway; we fail early + clearly).
11
+ *
12
+ * Dependency-free: STORED entries are read directly; DEFLATE entries use the
13
+ * platform `DecompressionStream` when present (browsers + Node ≥18). We don't
14
+ * bundle a zlib.
15
+ */
16
+ /**
17
+ * Read a `.dope` zip → the fully-inlined effect document (a parsed object).
18
+ * Resolves the effect JSON via manifest.json, then inlines relative `$ref`/asset
19
+ * paths under geometry/outlines from the zip's `assets/`.
20
+ */
21
+ export declare function readDopeZip(buf: Uint8Array): Promise<object>;
22
+ //# sourceMappingURL=dope-zip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dope-zip.d.ts","sourceRoot":"","sources":["../../src/framework/dope-zip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA2DH;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8ClE"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Minimal `.dope` zip (dotLottie-style) reader.
3
+ *
4
+ * A distributed `.dope` may be a zip: a `manifest.json` naming the effect doc
5
+ * plus an `assets/` dir referenced by RELATIVE paths only. This reads the zip
6
+ * entries (STORED or DEFLATE), finds the effect JSON via the manifest (or the
7
+ * first `*.json` under `effects/`), and resolves any relative asset `$ref`s in
8
+ * `geometry.outlines.*` by inlining them as `data:` URIs — so the doc handed to
9
+ * the loader is fully self-contained. Remote/absolute refs are rejected (the
10
+ * loader's standalone guard would reject them anyway; we fail early + clearly).
11
+ *
12
+ * Dependency-free: STORED entries are read directly; DEFLATE entries use the
13
+ * platform `DecompressionStream` when present (browsers + Node ≥18). We don't
14
+ * bundle a zlib.
15
+ */
16
+ function u16(b, o) {
17
+ return b[o] | (b[o + 1] << 8);
18
+ }
19
+ function u32(b, o) {
20
+ return (b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)) >>> 0;
21
+ }
22
+ /** Parse the local-file-header records of a zip into raw entries. */
23
+ function parseZip(buf) {
24
+ const entries = [];
25
+ const dec = new TextDecoder();
26
+ let i = 0;
27
+ while (i + 4 <= buf.length) {
28
+ const sig = u32(buf, i);
29
+ if (sig !== 0x04034b50)
30
+ break; // local file header; central dir / EOCD follow
31
+ const method = u16(buf, i + 8);
32
+ const compSize = u32(buf, i + 18);
33
+ const nameLen = u16(buf, i + 26);
34
+ const extraLen = u16(buf, i + 28);
35
+ const nameStart = i + 30;
36
+ const name = dec.decode(buf.subarray(nameStart, nameStart + nameLen));
37
+ const dataStart = nameStart + nameLen + extraLen;
38
+ const data = buf.subarray(dataStart, dataStart + compSize);
39
+ entries.push({ name, method, data });
40
+ i = dataStart + compSize;
41
+ }
42
+ if (entries.length === 0)
43
+ throw new Error("dope: not a zip (no local file headers)");
44
+ return entries;
45
+ }
46
+ async function inflate(entry) {
47
+ if (entry.method === 0)
48
+ return entry.data.slice(); // STORED
49
+ if (entry.method === 8) {
50
+ // raw DEFLATE
51
+ if (typeof globalThis.DecompressionStream === "undefined") {
52
+ throw new Error("dope: DEFLATE zip entry but no DecompressionStream available");
53
+ }
54
+ const DS = globalThis
55
+ .DecompressionStream;
56
+ const ds = new DS("deflate-raw");
57
+ // Copy into a standalone ArrayBuffer-backed view for the Blob part.
58
+ const part = new Uint8Array(entry.data.length);
59
+ part.set(entry.data);
60
+ const stream = new Response(new Blob([part]).stream().pipeThrough(ds));
61
+ return new Uint8Array(await stream.arrayBuffer());
62
+ }
63
+ throw new Error(`dope: unsupported zip compression method ${entry.method}`);
64
+ }
65
+ const ABS_OR_REMOTE = /^(?:[a-z][a-z0-9+.-]*:)?\/\/|^\//i;
66
+ /**
67
+ * Read a `.dope` zip → the fully-inlined effect document (a parsed object).
68
+ * Resolves the effect JSON via manifest.json, then inlines relative `$ref`/asset
69
+ * paths under geometry/outlines from the zip's `assets/`.
70
+ */
71
+ export async function readDopeZip(buf) {
72
+ const entries = parseZip(buf);
73
+ const files = new Map();
74
+ for (const e of entries)
75
+ files.set(e.name.replace(/^\.?\//, ""), e);
76
+ const textOf = async (name) => {
77
+ const e = files.get(name);
78
+ if (!e)
79
+ throw new Error(`dope zip: missing entry "${name}"`);
80
+ return new TextDecoder().decode(await inflate(e));
81
+ };
82
+ // Locate the effect doc.
83
+ let effectPath;
84
+ if (files.has("manifest.json")) {
85
+ const manifest = JSON.parse(await textOf("manifest.json"));
86
+ effectPath = manifest.effects?.[0]?.path?.replace(/^\.?\//, "");
87
+ }
88
+ if (!effectPath) {
89
+ effectPath = [...files.keys()].find((n) => /^effects\/.+\.json$/.test(n)) ?? "effect.json";
90
+ }
91
+ const doc = JSON.parse(await textOf(effectPath));
92
+ // Inline relative asset refs in geometry.outlines.*.sdf when stored as a path.
93
+ const geo = doc.geometry;
94
+ if (geo?.outlines) {
95
+ for (const outline of Object.values(geo.outlines)) {
96
+ const sdf = outline.sdf;
97
+ const ref = (sdf?.data ?? outline.sdfRef);
98
+ if (typeof ref === "string" && !ref.startsWith("data:")) {
99
+ if (ABS_OR_REMOTE.test(ref)) {
100
+ throw new Error(`dope zip: outline asset must be a relative path, got "${ref}"`);
101
+ }
102
+ const e = files.get(ref.replace(/^\.?\//, ""));
103
+ if (!e)
104
+ throw new Error(`dope zip: missing asset "${ref}"`);
105
+ const bytes = await inflate(e);
106
+ const b64 = typeof Buffer !== "undefined"
107
+ ? Buffer.from(bytes).toString("base64")
108
+ : btoa(String.fromCharCode(...bytes));
109
+ if (sdf)
110
+ sdf.data = `data:application/octet-stream;base64,${b64}`;
111
+ }
112
+ }
113
+ }
114
+ return doc;
115
+ }
116
+ //# sourceMappingURL=dope-zip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dope-zip.js","sourceRoot":"","sources":["../../src/framework/dope-zip.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The Dopamine effect framework — the backbone every visual effect plugs into.
3
+ *
4
+ * An *effect* (Solarbloom, Calligraphic Verdict, Comic Impact, and future
5
+ * progress / error / attention effects) is a self-contained module that knows
6
+ * two things:
7
+ *
8
+ * 1. how to turn the human-facing "feeling" knobs (mood / intensity / whimsy)
9
+ * plus a seed into its own concrete, deterministic render parameters, and
10
+ * 2. how to draw a single frame at an arbitrary `elapsedMs` into a shared GPU
11
+ * surface — both the light pass and (if present) the multiply shadow pass.
12
+ *
13
+ * Effects never create the DOM overlay, the GL context, or the RAF loop — the
14
+ * runtime (the conductor) owns all of that. That separation is what lets a new
15
+ * effect be a small file that self-registers, and keeps the library
16
+ * tree-shakeable: importing one effect pulls in nothing from the others.
17
+ */
18
+ import type { GLContext } from "../engine/context.js";
19
+ import type { ResolvedMood } from "./mood-registry.js";
20
+ /** Origin/anchor in CSS pixels, relative to the render surface's top-left. */
21
+ export interface Anchor {
22
+ x: number;
23
+ y: number;
24
+ }
25
+ /**
26
+ * The "feeling" API, shared by every effect. Individual effects map these onto
27
+ * their own low-level parameters via {@link EffectFactory.resolve}. This is the
28
+ * deliberate seam between *what a developer expresses* (a feeling) and *what the
29
+ * shader consumes* (numbers).
30
+ */
31
+ export interface FeelingInput {
32
+ /** Emotional register — a registered mood name. */
33
+ mood: string;
34
+ /** 0..1 — arousal/valence: saturation, brightness, scale, overshoot. */
35
+ intensity: number;
36
+ /** 0..1 — stylization: photoreal (0) ↔ cel / hand-drawn "animate on twos" (1). */
37
+ whimsy: number;
38
+ /** Deterministic seed for the algorithmic color + motion. */
39
+ seed: number;
40
+ }
41
+ /**
42
+ * Everything an effect instance needs to draw, supplied by the runtime. The
43
+ * effect draws its light pass into `light` and, when `shadow` is non-null, its
44
+ * occlusion silhouette into `shadow` (a separate `mix-blend-mode: multiply`
45
+ * canvas + context). Both contexts are persistent + program-cached.
46
+ */
47
+ export interface EffectContext {
48
+ /** Shared WebGL2 light context (`screen` blend) + program cache. */
49
+ readonly light: GLContext;
50
+ /** Shared WebGL2 shadow context (`multiply` blend), or null if disabled. */
51
+ readonly shadow: GLContext | null;
52
+ /** Where the effect is anchored, in CSS px relative to the surface. */
53
+ readonly anchor: Anchor;
54
+ /**
55
+ * Size (CSS px) of the underlying element the effect targets, centred on
56
+ * {@link anchor}. The centrepiece (checkmark, ✗, comic word, hero heart, ink
57
+ * gesture) is sized to THIS box so it matches the page element. Omitted ⇒ the
58
+ * full render surface (the centrepiece fills the canvas, as before).
59
+ */
60
+ readonly targetSize?: {
61
+ width: number;
62
+ height: number;
63
+ };
64
+ /** Device-pixel ratio to render at (already capped by the runtime). */
65
+ readonly dpr: number;
66
+ /**
67
+ * Present when the host composites against a known backdrop colour rather than
68
+ * the default `mix-blend-mode: screen`. The runners then emit PREMULTIPLIED
69
+ * light (alpha = brightness) on the light pass so the effect stays visible on
70
+ * any surface — white included — composited source-over. Absent ⇒ the classic
71
+ * screen/opaque path (byte-identical to before).
72
+ */
73
+ readonly composite?: {
74
+ premultiplied: boolean;
75
+ };
76
+ }
77
+ /** A live, drawable effect. Pure function of time: same `elapsedMs` → same frame. */
78
+ export interface EffectInstance {
79
+ /** Total length in ms after which the effect has fully played out. */
80
+ readonly durationMs: number;
81
+ /** Draw the frame at `elapsedMs` since the effect started. */
82
+ renderAt(elapsedMs: number): void;
83
+ /** Release any per-instance GPU resources (not shared/cached ones). */
84
+ dispose(): void;
85
+ }
86
+ /**
87
+ * The contract a new effect implements. `Params` is the effect's private,
88
+ * fully-resolved parameter shape — opaque to the runtime.
89
+ */
90
+ export interface EffectFactory<Params = unknown> {
91
+ /** Stable, unique id, e.g. `"solarbloom"`. Used by the registry + API. */
92
+ readonly name: string;
93
+ /**
94
+ * Map the shared feeling knobs + a resolved mood into this effect's own
95
+ * deterministic params. Pure — no DOM, no GL, no randomness beyond the seed.
96
+ */
97
+ resolve(feeling: FeelingInput, mood: ResolvedMood): Params;
98
+ /** Build a drawable instance for the given resolved params + context. */
99
+ create(params: Params, ctx: EffectContext): EffectInstance;
100
+ /**
101
+ * Whether this effect wants a shadow (multiply) companion canvas. Defaults to
102
+ * true; an effect that casts no shadow can opt out to skip the second context.
103
+ */
104
+ readonly castsShadow?: boolean;
105
+ /**
106
+ * Optional reduced-motion handling: which `elapsedMs` of the timeline best
107
+ * represents a calm peak, and how long to hold that single static frame
108
+ * instead of animating. Sensible defaults are used if omitted.
109
+ */
110
+ readonly reducedMotion?: {
111
+ /** How long to hold the minimal frame, ms. Default 360. */
112
+ holdMs?: number;
113
+ /** Which `elapsedMs` of the full timeline best represents a calm peak. */
114
+ peakMs?: number;
115
+ };
116
+ /**
117
+ * CONTINUOUS-loop contract (from `tempo.loop`): the effect repeats seamlessly
118
+ * with this period and `durationMs` is a whole number of periods. The
119
+ * conductor re-arms it at `durationMs` instead of tearing down — the host
120
+ * stops it via the handle `play()` returns. Absent for one-shot effects.
121
+ */
122
+ readonly loop?: {
123
+ periodMs: number;
124
+ };
125
+ }
126
+ /** Public alias: an `Effect` is what you register and play by name. */
127
+ export type Effect<Params = unknown> = EffectFactory<Params>;
128
+ //# sourceMappingURL=effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/framework/effect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,8EAA8E;AAC9E,MAAM,WAAW,MAAM;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC;CACjD;AAED,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,uEAAuE;IACvE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,MAAM,GAAG,OAAO;IAC7C,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC;IAC3D,yEAAyE;IACzE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,cAAc,CAAC;IAC3D;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE;QACvB,2DAA2D;QAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,0EAA0E;QAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACtC;AAED,uEAAuE;AACvE,MAAM,MAAM,MAAM,CAAC,MAAM,GAAG,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The Dopamine effect framework — the backbone every visual effect plugs into.
3
+ *
4
+ * An *effect* (Solarbloom, Calligraphic Verdict, Comic Impact, and future
5
+ * progress / error / attention effects) is a self-contained module that knows
6
+ * two things:
7
+ *
8
+ * 1. how to turn the human-facing "feeling" knobs (mood / intensity / whimsy)
9
+ * plus a seed into its own concrete, deterministic render parameters, and
10
+ * 2. how to draw a single frame at an arbitrary `elapsedMs` into a shared GPU
11
+ * surface — both the light pass and (if present) the multiply shadow pass.
12
+ *
13
+ * Effects never create the DOM overlay, the GL context, or the RAF loop — the
14
+ * runtime (the conductor) owns all of that. That separation is what lets a new
15
+ * effect be a small file that self-registers, and keeps the library
16
+ * tree-shakeable: importing one effect pulls in nothing from the others.
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/framework/effect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Per-FRAME expression evaluator — the datafied form of an effect's `frame()` /
3
+ * `shadowHeightFrac` logic hooks.
4
+ *
5
+ * The resolve-time grammar (`loader.ts` `evalExpr`) maps a feeling into the
6
+ * resolved param bag ONCE per fire. This module is its per-frame sibling: it
7
+ * evaluates the `.dope` `tempo.frame` / `render.shadowHeightFrac` expression
8
+ * trees EVERY frame against the live clocks (`animMs` / `life` / `elapsedMs`,
9
+ * plus the `loopS` / `phase` loop clocks for effects with `tempo.loop`) and
10
+ * the resolved params — so the per-frame logic, like the resolve mapping,
11
+ * is authored once in the `.dope` and interpreted identically on every
12
+ * platform. The same grammar also powers the PER-PASS `render.pass`
13
+ * expressions ({@link evalPassExpr}): params plus the pass-geometry inputs
14
+ * (`targetMinDimPx` / `sdfRange` / `sdfViewBoxW`), with the frame clocks
15
+ * rejected — pass values are computed once per pass, not per frame.
16
+ *
17
+ * Like `evalExpr`, nodes are evaluated RAW (no decode step) and anything
18
+ * outside the grammar THROWS. The tempo primitives (`envelope`, `easeOutBack`,
19
+ * `easeOutCubic`, `clamp01`) are the SAME functions the hand-written hooks
20
+ * called (imported from `engine/tempo.ts`), so a datafied effect's output is
21
+ * bit-identical to the code it replaced.
22
+ */
23
+ /** The per-frame expression grammar — an expression tree over the frame ctx. */
24
+ export type FrameExprNode = number | {
25
+ const: number;
26
+ } | {
27
+ param: string;
28
+ } | {
29
+ input: "animMs" | "life" | "elapsedMs" | "loopS" | "phase" | "targetMinDimPx" | "sdfRange" | "sdfViewBoxW" | "dpr";
30
+ } | {
31
+ add: FrameExprNode[];
32
+ } | {
33
+ sub: FrameExprNode[];
34
+ } | {
35
+ mul: FrameExprNode[];
36
+ } | {
37
+ div: FrameExprNode[];
38
+ } | {
39
+ min: FrameExprNode[];
40
+ } | {
41
+ max: FrameExprNode[];
42
+ } | {
43
+ pow: [FrameExprNode, FrameExprNode];
44
+ } | {
45
+ sin: FrameExprNode;
46
+ } | {
47
+ cos: FrameExprNode;
48
+ } | {
49
+ exp: FrameExprNode;
50
+ } | {
51
+ clamp01: FrameExprNode;
52
+ } | {
53
+ lt: [FrameExprNode, FrameExprNode, FrameExprNode, FrameExprNode];
54
+ } | {
55
+ envelope: [FrameExprNode, FrameExprNode];
56
+ } | {
57
+ easeOutCubic: FrameExprNode;
58
+ } | {
59
+ easeOutBack: [FrameExprNode, FrameExprNode];
60
+ };
61
+ /** Evaluation context for a per-frame expression. */
62
+ export interface FrameExprCtx {
63
+ /** The "on twos"-snapped animation clock in ms (stepping already applied). */
64
+ animMs: number;
65
+ /** Normalized life 0..1 (animMs / durationMs, clamped). */
66
+ life: number;
67
+ /** The REAL un-stepped wall clock in ms (mirrors the Swift/Android runners). */
68
+ elapsedMs: number;
69
+ /** The resolved render-param bag (numeric entries are addressable). */
70
+ params: Record<string, unknown>;
71
+ /**
72
+ * Seconds within the current loop (`(animMs % tempo.loop.periodMs) / 1000`).
73
+ * 0 for an effect with no `tempo.loop` — the caller (the dope-pass frame
74
+ * derivation) fills these from the doc's loop contract.
75
+ */
76
+ loopS?: number;
77
+ /** Normalized loop phase in [0, 1) (`animMs % periodMs / periodMs`); 0 without a loop. */
78
+ phase?: number;
79
+ /** Pass-geometry inputs (see {@link PassExprInputs}); only read in "pass" mode. */
80
+ pass?: PassExprInputs;
81
+ }
82
+ /**
83
+ * The pass-geometry inputs a `render.pass` expression may read (evaluated ONCE
84
+ * per pass by the runners, never per resolve or per frame).
85
+ */
86
+ export interface PassExprInputs {
87
+ /**
88
+ * Min dimension of the TARGETED element box in device px, falling back to
89
+ * the full canvas when untargeted — the same target-fallback the standard
90
+ * `uTarget` uniform uses, so a pass-sized centrepiece tracks the element.
91
+ */
92
+ targetMinDimPx: number;
93
+ /**
94
+ * The declared `range` of the SDF behind the first `binding.samplers` entry
95
+ * with an `outline` source (author units → the full byte range); 0 when no
96
+ * sampler declares one.
97
+ */
98
+ sdfRange: number;
99
+ /** That SDF's `viewBox[2]` (author-units width); 0 when absent. */
100
+ sdfViewBoxW: number;
101
+ /**
102
+ * The device-pixel ratio (web `devicePixelRatio` / Android `density` / the
103
+ * Metal layer's content scale) the surface renders at — so a pass value can
104
+ * be expressed in CSS-ish units and scaled to device px (e.g. heartburst's
105
+ * halftone cell `uDotSize = dotSize · dpr`).
106
+ */
107
+ dpr: number;
108
+ }
109
+ /** Evaluate a per-frame grammar node to a number. Pure; throws outside the grammar. */
110
+ export declare function evalFrameExpr(node: FrameExprNode, ctx: FrameExprCtx): number;
111
+ /**
112
+ * Evaluate a PARAMS-ONLY expression (e.g. `render.shadowHeightFrac`): the same
113
+ * grammar, but `{input}` nodes THROW — a shadow-geometry expression must be a
114
+ * pure function of the resolved params, never of the frame clock.
115
+ */
116
+ export declare function evalParamExpr(node: FrameExprNode, params: Record<string, unknown>): number;
117
+ /**
118
+ * Evaluate a PER-PASS expression (`render.pass`): the same grammar over the
119
+ * resolved params plus the pass-geometry inputs (`targetMinDimPx` / `sdfRange`
120
+ * / `sdfViewBoxW`). Frame clocks (`animMs` / `life` / …) THROW — a pass
121
+ * expression is evaluated once per pass, not per frame.
122
+ */
123
+ export declare function evalPassExpr(node: FrameExprNode, params: Record<string, unknown>, pass: PassExprInputs): number;
124
+ //# sourceMappingURL=frame-expr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-expr.d.ts","sourceRoot":"","sources":["../../src/framework/frame-expr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,gFAAgF;AAChF,MAAM,MAAM,aAAa,GACrB,MAAM,GACN;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IACE,KAAK,EACD,QAAQ,GACR,MAAM,GACN,WAAW,GACX,OAAO,GACP,OAAO,GAGP,gBAAgB,GAChB,UAAU,GACV,aAAa,GACb,KAAK,CAAC;CACX,GACD;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,GAAG,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;CAAE,GACvC;IAAE,GAAG,EAAE,aAAa,CAAA;CAAE,GACtB;IAAE,GAAG,EAAE,aAAa,CAAA;CAAE,GACtB;IAAE,GAAG,EAAE,aAAa,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC,CAAA;CAAE,GACpE;IAAE,QAAQ,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;CAAE,GAC5C;IAAE,YAAY,EAAE,aAAa,CAAA;CAAE,GAC/B;IAAE,WAAW,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAA;CAAE,CAAC;AAEpD,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;CACb;AAkFD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE1F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,cAAc,GACnB,MAAM,CAER"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Per-FRAME expression evaluator — the datafied form of an effect's `frame()` /
3
+ * `shadowHeightFrac` logic hooks.
4
+ *
5
+ * The resolve-time grammar (`loader.ts` `evalExpr`) maps a feeling into the
6
+ * resolved param bag ONCE per fire. This module is its per-frame sibling: it
7
+ * evaluates the `.dope` `tempo.frame` / `render.shadowHeightFrac` expression
8
+ * trees EVERY frame against the live clocks (`animMs` / `life` / `elapsedMs`,
9
+ * plus the `loopS` / `phase` loop clocks for effects with `tempo.loop`) and
10
+ * the resolved params — so the per-frame logic, like the resolve mapping,
11
+ * is authored once in the `.dope` and interpreted identically on every
12
+ * platform. The same grammar also powers the PER-PASS `render.pass`
13
+ * expressions ({@link evalPassExpr}): params plus the pass-geometry inputs
14
+ * (`targetMinDimPx` / `sdfRange` / `sdfViewBoxW`), with the frame clocks
15
+ * rejected — pass values are computed once per pass, not per frame.
16
+ *
17
+ * Like `evalExpr`, nodes are evaluated RAW (no decode step) and anything
18
+ * outside the grammar THROWS. The tempo primitives (`envelope`, `easeOutBack`,
19
+ * `easeOutCubic`, `clamp01`) are the SAME functions the hand-written hooks
20
+ * called (imported from `engine/tempo.ts`), so a datafied effect's output is
21
+ * bit-identical to the code it replaced.
22
+ */
23
+ import { clamp01, easeOutBack, easeOutCubic, envelope } from "../engine/tempo.js";
24
+ const FRAME_INPUTS = ["animMs", "life", "elapsedMs", "loopS", "phase"];
25
+ const PASS_INPUTS = ["targetMinDimPx", "sdfRange", "sdfViewBoxW", "dpr"];
26
+ function evalInput(name, ctx, mode) {
27
+ const isFrame = FRAME_INPUTS.includes(name);
28
+ const isPass = PASS_INPUTS.includes(name);
29
+ if (mode === "pass") {
30
+ if (isFrame) {
31
+ throw new Error(`dope: frame input "${name}" is not allowed in a render.pass expression (pass expressions are not frame-clocked)`);
32
+ }
33
+ if (isPass)
34
+ return ctx.pass?.[name] ?? 0;
35
+ throw new Error(`dope: unknown frame input "${name}"`);
36
+ }
37
+ if (isPass) {
38
+ throw new Error(`dope: pass input "${name}" is only allowed in a render.pass expression`);
39
+ }
40
+ if (mode === "params") {
41
+ throw new Error(`dope: {input} is not allowed in a params-only expression (got "${name}")`);
42
+ }
43
+ if (name === "animMs")
44
+ return ctx.animMs;
45
+ if (name === "life")
46
+ return ctx.life;
47
+ if (name === "elapsedMs")
48
+ return ctx.elapsedMs;
49
+ if (name === "loopS")
50
+ return ctx.loopS ?? 0;
51
+ if (name === "phase")
52
+ return ctx.phase ?? 0;
53
+ throw new Error(`dope: unknown frame input "${name}"`);
54
+ }
55
+ function evalNode(node, ctx, mode) {
56
+ if (typeof node === "number")
57
+ return node;
58
+ if ("const" in node)
59
+ return node.const;
60
+ if ("param" in node) {
61
+ const raw = ctx.params[node.param];
62
+ if (typeof raw !== "number") {
63
+ throw new Error(`dope: frame expr references missing/non-numeric param "${node.param}"`);
64
+ }
65
+ return Number(raw);
66
+ }
67
+ if ("input" in node)
68
+ return evalInput(String(node.input), ctx, mode);
69
+ if ("add" in node)
70
+ return node.add.reduce((p, n) => p + evalNode(n, ctx, mode), 0);
71
+ if ("sub" in node) {
72
+ const parts = node.sub.map((n) => evalNode(n, ctx, mode));
73
+ return parts.slice(1).reduce((p, n) => p - n, parts[0] ?? 0);
74
+ }
75
+ if ("mul" in node)
76
+ return node.mul.reduce((p, n) => p * evalNode(n, ctx, mode), 1);
77
+ if ("div" in node) {
78
+ const parts = node.div.map((n) => evalNode(n, ctx, mode));
79
+ return parts.slice(1).reduce((p, n) => p / n, parts[0] ?? 0);
80
+ }
81
+ if ("min" in node)
82
+ return Math.min(...node.min.map((n) => evalNode(n, ctx, mode)));
83
+ if ("max" in node)
84
+ return Math.max(...node.max.map((n) => evalNode(n, ctx, mode)));
85
+ if ("pow" in node) {
86
+ return Math.pow(evalNode(node.pow[0], ctx, mode), evalNode(node.pow[1], ctx, mode));
87
+ }
88
+ if ("sin" in node)
89
+ return Math.sin(evalNode(node.sin, ctx, mode));
90
+ if ("cos" in node)
91
+ return Math.cos(evalNode(node.cos, ctx, mode));
92
+ if ("exp" in node)
93
+ return Math.exp(evalNode(node.exp, ctx, mode));
94
+ if ("clamp01" in node)
95
+ return clamp01(evalNode(node.clamp01, ctx, mode));
96
+ if ("lt" in node) {
97
+ // Branches are evaluated LAZILY (only the taken branch), so a guard like
98
+ // `0 < elapsedMs ? f(elapsedMs) : 0` never evaluates f outside its domain.
99
+ const [a, b, then, otherwise] = node.lt;
100
+ return evalNode(a, ctx, mode) < evalNode(b, ctx, mode)
101
+ ? evalNode(then, ctx, mode)
102
+ : evalNode(otherwise, ctx, mode);
103
+ }
104
+ if ("envelope" in node) {
105
+ return envelope(evalNode(node.envelope[0], ctx, mode), evalNode(node.envelope[1], ctx, mode));
106
+ }
107
+ if ("easeOutCubic" in node)
108
+ return easeOutCubic(evalNode(node.easeOutCubic, ctx, mode));
109
+ if ("easeOutBack" in node) {
110
+ return easeOutBack(evalNode(node.easeOutBack[0], ctx, mode), evalNode(node.easeOutBack[1], ctx, mode));
111
+ }
112
+ throw new Error(`dope: unknown frame expr node ${JSON.stringify(node)}`);
113
+ }
114
+ /** Evaluate a per-frame grammar node to a number. Pure; throws outside the grammar. */
115
+ export function evalFrameExpr(node, ctx) {
116
+ return evalNode(node, ctx, "frame");
117
+ }
118
+ /**
119
+ * Evaluate a PARAMS-ONLY expression (e.g. `render.shadowHeightFrac`): the same
120
+ * grammar, but `{input}` nodes THROW — a shadow-geometry expression must be a
121
+ * pure function of the resolved params, never of the frame clock.
122
+ */
123
+ export function evalParamExpr(node, params) {
124
+ return evalNode(node, { animMs: 0, life: 0, elapsedMs: 0, params }, "params");
125
+ }
126
+ /**
127
+ * Evaluate a PER-PASS expression (`render.pass`): the same grammar over the
128
+ * resolved params plus the pass-geometry inputs (`targetMinDimPx` / `sdfRange`
129
+ * / `sdfViewBoxW`). Frame clocks (`animMs` / `life` / …) THROW — a pass
130
+ * expression is evaluated once per pass, not per frame.
131
+ */
132
+ export function evalPassExpr(node, params, pass) {
133
+ return evalNode(node, { animMs: 0, life: 0, elapsedMs: 0, params, pass }, "pass");
134
+ }
135
+ //# sourceMappingURL=frame-expr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-expr.js","sourceRoot":"","sources":["../../src/framework/frame-expr.ts"],"names":[],"mappings":"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"}