@zakkster/lite-scratch-fx 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.1] - 2026-08-22
4
+
5
+ ### Fixed
6
+ - **`PeelRecipe`** -- removed a per-frame forced synchronous layout read. `tick`
7
+ previously read `src.offsetWidth` (a layout property) inside the transform
8
+ template string on every frame, immediately after writing `style.transform` /
9
+ `style.transformOrigin`, forcing a synchronous reflow each frame while a peel
10
+ reveal was active. The source layer's logical width is now captured once in
11
+ `init` (the engine already measures it to size the effect canvas and passes it
12
+ as `w`) and reused, so the per-frame `tick` body is write-only. No visual or
13
+ API change.
14
+
15
+ ## [1.3.0] - 2026-08-22
16
+
17
+ Abortable, self-restoring reveals. A reveal can now be cancelled, and the host scratch
18
+ layer is returned to exactly the inline style it had before the reveal on completion,
19
+ cancel, AND destroy -- so a grid can reset every round. Purely additive.
20
+
21
+ ### Added
22
+ - **`controller.cancel()`** on both the standalone controller and stage-managed
23
+ controllers -- abort an in-flight reveal: stop the effect, restore the host canvas's
24
+ inline style, and do NOT fire `onDone`; the controller is revealable again afterwards.
25
+ A no-op when idle or destroyed. A stage `cancel()` does not touch other controllers'
26
+ in-flight reveals and does not reclaim the sub-range. See
27
+ `decisions/0002-host-style-and-cancel.md`.
28
+ - `src/HostStyle.js` -- `captureHostStyle` / `restoreHostStyle`, a shared cold-path helper
29
+ that snapshots and restores the host layer's 5 inline style props.
30
+ - **`dpr` option** for sharp rendering on high-DPI displays; recipes still author in CSS pixels.
31
+
32
+ ### Fixed
33
+ - **Recipes stranded inline style on the host `sourceCanvas`.** The reveal recipes write
34
+ `opacity`, `transform`, `filter`, `clipPath`, and `transformOrigin` on the host layer
35
+ during `tick`; `opacity` was never restored and the others only on the completion frame,
36
+ so an aborted (or opacity-fading) reveal left the cover invisible or transformed forever.
37
+ The controller now snapshots those 5 props at reveal start and restores them on every
38
+ terminal transition -- completion, cancel, and destroy -- via a single `endReveal`
39
+ terminator. No inline `opacity`/`transform`/`filter`/`clipPath`/`transformOrigin`
40
+ survives a reveal.
41
+
3
42
  ## [1.2.0] - 2026-08-22
4
43
 
5
44
  Concurrent scratch-card reveals over one shared particle pool. Purely additive --
6
45
  `createScratchController` and its options are unchanged.
7
46
 
8
47
  ### Added
9
- - **`createScratchStage({ maxParticles, seed })`** a stage that owns one particle
48
+ - **`createScratchStage({ maxParticles, seed })`** -- a stage that owns one particle
10
49
  engine and lets any number of controllers reveal **at the same time** over that one
11
50
  pool (where `{ engine }` allowed only one reveal at a time). Each
12
51
  `stage.createController(src, fx, { capacity })` reserves a fixed sub-range of the lanes,
@@ -14,8 +53,10 @@ Concurrent scratch-card reveals over one shared particle pool. Purely additive -
14
53
  and keeps its own deterministic RNG. `stage.tick(dt)` (seconds) drives every active reveal
15
54
  from one clock; reserving past `maxParticles` throws. Stage-managed controllers have no
16
55
  `tick` of their own. See `decisions/0001-concurrent-shared-reveals.md`.
17
- - `src/PixelScan.js` the reveal-scan logic extracted into a shared `createPixelScanner`,
56
+ - `src/PixelScan.js` -- the reveal-scan logic extracted into a shared `createPixelScanner`,
18
57
  now used by both the controller and the stage (no behaviour change).
58
+ - A `VERSION`-matches-`package.json` test, and a README rebuilt on the LiteSepforge
59
+ documentation spine. No API or behaviour change.
19
60
 
20
61
  ### Notes
21
62
  - Feasible with no change to `@zakkster/lite-soa-particle-engine`: raw-mode `tick(dt)` is
@@ -25,28 +66,28 @@ Concurrent scratch-card reveals over one shared particle pool. Purely additive -
25
66
  ## [1.1.0] - 2026-08-22
26
67
 
27
68
  Recipe organization and an extensible registry, modeled on `@zakkster/lite-ambient-fx`.
28
- No behaviour change to any recipe all 21 bodies are byte-identical to 1.0.0.
69
+ No behaviour change to any recipe -- all 21 bodies are byte-identical to 1.0.0.
29
70
 
30
71
  ### Added
31
- - **`RECIPE_META`** a live array of `{ id, name, category, themeable, needsUntaintedCanvas }`
72
+ - **`RECIPE_META`** -- a live array of `{ id, name, category, themeable, needsUntaintedCanvas }`
32
73
  for every recipe, so a host can build a picker (group by `category`, filter by `themeable`)
33
74
  without hardcoding the list. Mirrors lite-ambient's `THEME_META`.
34
- - **`registerRecipe(id, factory, meta?)`** register a custom recipe or override a built-in;
75
+ - **`registerRecipe(id, factory, meta?)`** -- register a custom recipe or override a built-in;
35
76
  it lands in `RECIPES` and `RECIPE_META` immediately so existing pickers keep working.
36
77
  Mirrors lite-ambient's `registerTheme()`. Omitted meta fields fall back to the prior entry,
37
78
  then a de-camelCased name, `category: 'custom'`, and `false` flags.
38
79
  - `resolvePalette` is now exported (and typed) for authors writing themeable recipes.
39
- - **Host-driven mode** `createScratchController(src, fx, { driven: true })` skips the
80
+ - **Host-driven mode** -- `createScratchController(src, fx, { driven: true })` skips the
40
81
  engine's RAF loop; the host calls `controller.tick(dt)` (dt in seconds) so one page clock
41
82
  drives N controllers. `tick()` is a no-op unless driven and a reveal is active.
42
- - **Shared engine** `createScratchController(src, fx, { engine })` reuses a caller-supplied
83
+ - **Shared engine** -- `createScratchController(src, fx, { engine })` reuses a caller-supplied
43
84
  particle engine, so a grid of scratch boxes holds one lane pool instead of one per box.
44
85
  A shared engine serves one reveal at a time (a reveal on a busy shared engine is ignored);
45
86
  `destroy()` never tears down a caller-supplied engine. The two options compose.
46
87
 
47
88
  ### Changed
48
89
  - **The two recipe files are merged into one `src/ScratchRecipes.js`, grouped by family**
49
- (particle / image / beam / css) with section banners replacing the arbitrary
90
+ (particle / image / beam / css) with section banners -- replacing the arbitrary
50
91
  `ScratchRecipes.js` (12) + `ScratchRecipes2.js` (9) split. `ScratchRecipes2.js` is removed
51
92
  from the package. All imports resolve through `index.js`, so the public API is unchanged.
52
93
  - `RECIPES` is now an extensible null-prototype registry (was a frozen object) so
@@ -60,18 +101,18 @@ zero-GC line on the reveal path, and stands up the torture gate.
60
101
 
61
102
  ### Added
62
103
  - `createScratchController(sourceCanvas, effectCanvas, { maxParticles, seed, scanPrecision })`
63
- scans the still-covered pixels of a scratch layer for spawn points, runs a reveal
104
+ -- scans the still-covered pixels of a scratch layer for spawn points, runs a reveal
64
105
  recipe to completion, clears the overlay, and calls the completion callback.
65
106
  - 21 reveal recipes: `burn`, `shatter`, `dissolve`, `explode`, `dragonBreath`,
66
107
  `iceBreath`, `shineWave`, `lightningCrawl`, `shine`, `peel`, `fade`, `implosion`,
67
108
  `glitchReveal`, `matrixDecay`, `goldDust`, `pixelShatter`, `laserScan`,
68
109
  `confettiBlast`, `liquidMelt`, `neonPulse`, `cosmicDust`.
69
110
  - **Themeable palettes.** The 14 recipes with a visible palette take a unified
70
- `{ colors, theme }` pair pass a `{ light, mid, dark }` `theme` shorthand or an
111
+ `{ colors, theme }` pair -- pass a `{ light, mid, dark }` `theme` shorthand or an
71
112
  explicit `colors` ramp; `colors` wins, and both default to the recipe's own palette.
72
113
  Replaces three inconsistent conventions (`color`, `colors`, hardcoded). New
73
114
  `resolvePalette` helper in `src/Palette.js`.
74
- - `RECIPES` registry (short-name factory) and `RECIPE_NAMES` for data-driven pickers.
115
+ - `RECIPES` registry (short-name -> factory) and `RECIPE_NAMES` for data-driven pickers.
75
116
  - Full TypeScript declarations (`index.d.ts`), including `Theme` and `ThemeableRecipeOptions`.
76
117
  - Test suite under `node:test` (46 checks): controller lifecycle, the `data[]` dataFlag
77
118
  indexing invariant, a reveal-to-completion smoke test for every recipe, and theming
@@ -84,7 +125,7 @@ zero-GC line on the reveal path, and stands up the torture gate.
84
125
 
85
126
  ### Changed
86
127
  - **Zero-GC reveal path.** `scanPixels` no longer mints an offscreen canvas or an
87
- object per surviving pixel on every reveal the scan canvas is built once and the
128
+ object per surviving pixel on every reveal -- the scan canvas is built once and the
88
129
  spawn points fill preallocated `Float32Array`s read through one reused `spot` object.
89
130
  The per-frame `tick` no longer allocates a fresh particle-view object; it reuses one.
90
131
  - Test runner is `node:test` (was vitest); `vitest` and `typescript` devDependencies
@@ -97,7 +138,7 @@ zero-GC line on the reveal path, and stands up the torture gate.
97
138
  `engine._loop` every frame, but the SoA engine already owns its own
98
139
  `requestAnimationFrame` loop and computes its own `dt`. Driving it a second time
99
140
  corrupted `dt`, so particle life never decayed and the completion condition
100
- (`alive === 0 && elapsed >= duration`) was never met a reveal would run forever. The
141
+ (`alive === 0 && elapsed >= duration`) was never met -- a reveal would run forever. The
101
142
  external ticker is removed; the engine self-drives via `start()` / `stop()`.
102
143
  - **`count: 0` recipes spawned a full pool.** `recipe.count || maxParticles` turned an
103
144
  explicit `0` (pure-canvas reveals like `shine`, `implosion`, `laserScan`) into