@zakkster/lite-scratch-fx 1.6.0 → 1.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.0] - 2026-08-24
4
+
5
+ Optional per-frame glow cap for the two haloed particle recipes (B-20). Additive and
6
+ backward compatible: the knob defaults to `Infinity` (unlimited), so default output is
7
+ byte-identical to 1.6.0. The per-frame render path gains one counter and one guard.
8
+
9
+ ### Added
10
+ - **`glowBudget` option on `BurnRecipe` and `DragonBreathRecipe` (default `Infinity` =
11
+ unlimited).** Caps how many particles paint their outer glow halo per frame. Once the
12
+ budget is spent that tick, further particles draw only their brighter core arc; the
13
+ counter resets every frame. The core arc, physics, `alive` accounting, and the
14
+ offscreen cull are never capped. Uses a strict `g < glowBudget` compare (never a falsy
15
+ coercion), so `glowBudget: 0` means exactly zero halos (cores only) and the unset
16
+ default `Infinity` keeps every halo. `glowBudget: 80` matches the game's `GLOW_BUDGET`
17
+ parity. Zero new hot-path allocation.
18
+
3
19
  ## [1.6.0] - 2026-08-23
4
20
 
5
21
  Reduced-motion support (N-4). Backward compatible: the new controller/stage option
package/README.md CHANGED
@@ -268,8 +268,12 @@ current built-in look:
268
268
  - **`DragonBreathRecipe`** / **`IceBreathRecipe`** -- `spread` (jet half-angle in radians,
269
269
  defaults `0.3` / `0.225`), `speedMin`/`speedMax` (spawn speed range, defaults `12`-`25`
270
270
  / `15`-`28`).
271
- - **`PeelRecipe`** / **`ShineWaveRecipe`** -- `ease` (`(t: number) => number`): the easing
272
- applied to progress; defaults to the recipe's built-in curve (`easeIn` / `easeInOut`).
271
+ - **`BurnRecipe`** / **`DragonBreathRecipe`** -- `glowBudget` (default `Infinity` =
272
+ unlimited): a per-frame cap on how many particles paint their outer glow halo. The
273
+ brighter core arc, physics, and offscreen cull are never capped -- only the halo is
274
+ skipped once the budget is spent that frame; the counter resets every tick. `0` means
275
+ zero halos (cores only); `80` matches the game's `GLOW_BUDGET` parity. Default output is
276
+ byte-identical to earlier versions.
273
277
 
274
278
  ### Reduced motion
275
279
 
@@ -303,7 +307,7 @@ two ways to respond, plus a flag for building a picker.
303
307
 
304
308
  | Constant | Value | Meaning |
305
309
  | ------------------------- | ----------------------------------------------------------- | ---------------------------------------------------- |
306
- | `VERSION` | `'1.6.0'` | Package version string (synced to package.json). |
310
+ | `VERSION` | `'1.7.0'` | Package version string (synced to package.json). |
307
311
  | `maxParticles` (default) | `2000` | Controller / stage pool capacity. |
308
312
  | `scanPrecision` (default) | `32` | Horizontal resolution of the spawn-point scan. |
309
313
  | stage `capacity` (default)| `300` | Slots a stage controller reserves from the pool. |
package/index.d.ts CHANGED
@@ -242,11 +242,11 @@ export interface ThemeableRecipeOptions extends RecipeOptions {
242
242
  theme?: Theme;
243
243
  }
244
244
 
245
- export function BurnRecipe(opts?: ThemeableRecipeOptions): Recipe;
245
+ export function BurnRecipe(opts?: ThemeableRecipeOptions & { glowBudget?: number }): Recipe;
246
246
  export function ShatterRecipe(opts?: RecipeOptions & { gravity?: number }): Recipe;
247
247
  export function DissolveRecipe(opts?: ThemeableRecipeOptions & { fadeSpeed?: number }): Recipe;
248
248
  export function ExplodeRecipe(opts?: ThemeableRecipeOptions & { force?: number }): Recipe;
249
- export function DragonBreathRecipe(opts?: ThemeableRecipeOptions & { spread?: number; speedMin?: number; speedMax?: number }): Recipe;
249
+ export function DragonBreathRecipe(opts?: ThemeableRecipeOptions & { spread?: number; speedMin?: number; speedMax?: number; glowBudget?: number }): Recipe;
250
250
  export function IceBreathRecipe(opts?: ThemeableRecipeOptions & { spread?: number; speedMin?: number; speedMax?: number }): Recipe;
251
251
  export function ShineWaveRecipe(opts?: ThemeableRecipeOptions & { beamWidth?: number; particleCount?: number; ease?: (t: number) => number }): Recipe;
252
252
  export function LightningCrawlRecipe(opts?: ThemeableRecipeOptions & { boltCount?: number }): Recipe;
package/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  // Three-place version sync: this constant, package.json "version", and the top
15
15
  // CHANGELOG.md heading must always match. /release keeps them locked.
16
- export const VERSION = '1.6.0';
16
+ export const VERSION = '1.7.0';
17
17
 
18
18
  export { createScratchController } from './src/ScratchController.js';
19
19
  export { default as ScratchController } from './src/ScratchController.js';
package/llms.txt CHANGED
@@ -78,8 +78,11 @@ Every factory takes an options bag; all options default to the shipped tuning, s
78
78
  no args reproduces the stock effect. Beyond { colors, theme } (the 14 themeable recipes) and
79
79
  count/duration, the FX_CONFIG-style knobs are: dissolve fadeSpeed (fade window as a fraction of
80
80
  duration); dragonBreath and iceBreath spread / speedMin / speedMax (spawn cone half-angle in
81
- radians and speed range); peel and shineWave ease (a t -> t easing function). A non-function ease
82
- falls back to the recipe's default curve.
81
+ radians and speed range); burn and dragonBreath glowBudget (per-frame cap on how many particles
82
+ paint their outer glow halo, default Infinity = unlimited; 0 = zero halos, cores only; 80 =
83
+ game GLOW_BUDGET parity -- the core arc, physics and offscreen cull are never capped and the
84
+ counter resets each tick, so the default output is byte-identical to earlier versions); peel and
85
+ shineWave ease (a t -> t easing function). A non-function ease falls back to the recipe's default curve.
83
86
 
84
87
  ## Registry, metadata, extension
85
88
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zakkster/lite-scratch-fx",
3
3
  "author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
4
- "version": "1.6.0",
4
+ "version": "1.7.0",
5
5
  "description": "One-shot scratch-card reveal effects on canvas. A controller scans the still-covered pixels of a scratch layer, spawns particles from them, and delegates physics and rendering to a recipe. 21 themeable recipes (burn, shatter, dissolve, glitch, gold dust, cosmic dust, and more), a registry with a register hook, zero GSAP, a zero-GC hot path, deterministic seeded RNG, and a stage that runs a grid of simultaneous reveals over one shared pool.",
6
6
  "type": "module",
7
7
  "main": "./index.js",
@@ -28,7 +28,7 @@ import {resolvePalette} from './Palette.js';
28
28
  // Spawn particles from the scratch layer's still-covered pixels and run their own physics.
29
29
  // ===========================================================================
30
30
 
31
- export function BurnRecipe({count = 100, duration = 200, colors, theme} = {}) {
31
+ export function BurnRecipe({count = 100, duration = 200, glowBudget = Infinity, colors, theme} = {}) {
32
32
  let pSize, pDecay, pColorIdx;
33
33
  const palette = resolvePalette(colors, theme, ['#ff6b00', '#ff8c00', '#ffaa00', '#ff4500', '#8B0000']);
34
34
 
@@ -49,6 +49,7 @@ export function BurnRecipe({count = 100, duration = 200, colors, theme} = {}) {
49
49
  const {x, y, vx, vy, life, data, max} = engine;
50
50
  const ds = dt * 60;
51
51
  let alive = 0;
52
+ let g = 0;
52
53
  const progress = clamp(elapsed / duration, 0, 1);
53
54
  src.style.opacity = 1 - easeIn(progress);
54
55
 
@@ -63,10 +64,13 @@ export function BurnRecipe({count = 100, duration = 200, colors, theme} = {}) {
63
64
  if (y[i] < -50) continue;
64
65
  ctx.globalCompositeOperation = 'lighter';
65
66
  ctx.fillStyle = palette[pColorIdx[p]];
66
- ctx.globalAlpha = life[i] * 0.3;
67
- ctx.beginPath();
68
- ctx.arc(x[i], y[i], pSize[p] * 2, 0, Math.PI * 2);
69
- ctx.fill();
67
+ if (g < glowBudget) {
68
+ ctx.globalAlpha = life[i] * 0.3;
69
+ ctx.beginPath();
70
+ ctx.arc(x[i], y[i], pSize[p] * 2, 0, Math.PI * 2);
71
+ ctx.fill();
72
+ g++;
73
+ }
70
74
  ctx.globalAlpha = life[i];
71
75
  ctx.beginPath();
72
76
  ctx.arc(x[i], y[i], pSize[p], 0, Math.PI * 2);
@@ -171,7 +175,7 @@ export function ExplodeRecipe({count = 80, duration = 750, force = 15, colors, t
171
175
  }
172
176
 
173
177
 
174
- export function DragonBreathRecipe({count = 200, duration = 1200, spread = 0.3, speedMin = 12, speedMax = 25, colors, theme} = {}) {
178
+ export function DragonBreathRecipe({count = 200, duration = 1200, spread = 0.3, speedMin = 12, speedMax = 25, glowBudget = Infinity, colors, theme} = {}) {
175
179
  let pSize, pDecay, pColorIdx, r;
176
180
  const palette = resolvePalette(colors, theme, ['#FFF', '#FFD700', '#FF4500', '#8B0000', '#2F2F2F']);
177
181
 
@@ -203,6 +207,7 @@ export function DragonBreathRecipe({count = 200, duration = 1200, spread = 0.3,
203
207
  const dvy = Math.pow(0.98, ds);
204
208
  const dsz = Math.pow(1.04, ds);
205
209
  let alive = 0;
210
+ let g = 0;
206
211
  const fadeT = clamp((elapsed - 100) / 400, 0, 1);
207
212
  src.style.opacity = 1 - fadeT * fadeT;
208
213
 
@@ -220,10 +225,13 @@ export function DragonBreathRecipe({count = 200, duration = 1200, spread = 0.3,
220
225
  if (y[i] < -50) continue;
221
226
  ctx.globalCompositeOperation = 'lighter';
222
227
  ctx.fillStyle = palette[pColorIdx[p]];
223
- ctx.globalAlpha = life[i] * 0.3;
224
- ctx.beginPath();
225
- ctx.arc(x[i], y[i], pSize[p] * 2, 0, Math.PI * 2);
226
- ctx.fill();
228
+ if (g < glowBudget) {
229
+ ctx.globalAlpha = life[i] * 0.3;
230
+ ctx.beginPath();
231
+ ctx.arc(x[i], y[i], pSize[p] * 2, 0, Math.PI * 2);
232
+ ctx.fill();
233
+ g++;
234
+ }
227
235
  ctx.globalAlpha = life[i];
228
236
  ctx.beginPath();
229
237
  ctx.arc(x[i], y[i], pSize[p], 0, Math.PI * 2);