pixi-reels 2.5.0 → 2.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/{ReelSymbol-D698guvK.js → ReelSymbol-BvdqdXMY.js} +13 -3
  3. package/dist/ReelSymbol-BvdqdXMY.js.map +1 -0
  4. package/dist/ReelSymbol-CTlZ9ihf.cjs +2 -0
  5. package/dist/ReelSymbol-CTlZ9ihf.cjs.map +1 -0
  6. package/dist/{SpineSymbol-BL7iDOZj.js → SpineSymbol-C6FYNcwP.js} +2 -2
  7. package/dist/{SpineSymbol-BL7iDOZj.js.map → SpineSymbol-C6FYNcwP.js.map} +1 -1
  8. package/dist/{SpineSymbol-Dq3IAu_k.cjs → SpineSymbol-Do0zw5ad.cjs} +2 -2
  9. package/dist/{SpineSymbol-Dq3IAu_k.cjs.map → SpineSymbol-Do0zw5ad.cjs.map} +1 -1
  10. package/dist/board/BoardGrid.d.ts +69 -4
  11. package/dist/board/BoardGrid.d.ts.map +1 -1
  12. package/dist/board/HoldAndWinBoard.d.ts +12 -5
  13. package/dist/board/HoldAndWinBoard.d.ts.map +1 -1
  14. package/dist/board/HoldAndWinBuilder.d.ts +44 -4
  15. package/dist/board/HoldAndWinBuilder.d.ts.map +1 -1
  16. package/dist/board/HwTypes.d.ts +6 -0
  17. package/dist/board/HwTypes.d.ts.map +1 -1
  18. package/dist/config/types.d.ts +77 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/core/Reel.d.ts +12 -1
  21. package/dist/core/Reel.d.ts.map +1 -1
  22. package/dist/core/ReelSet.d.ts +1 -1
  23. package/dist/core/ReelSet.d.ts.map +1 -1
  24. package/dist/core/ReelSetBuilder.d.ts +30 -1
  25. package/dist/core/ReelSetBuilder.d.ts.map +1 -1
  26. package/dist/core/maskStrategies.d.ts +62 -5
  27. package/dist/core/maskStrategies.d.ts.map +1 -1
  28. package/dist/debug-BzQL0_Eq.cjs +4 -0
  29. package/dist/debug-BzQL0_Eq.cjs.map +1 -0
  30. package/dist/{debug-B0vmxmQR.js → debug-DJF_mzcM.js} +297 -255
  31. package/dist/debug-DJF_mzcM.js.map +1 -0
  32. package/dist/events/ReelEvents.d.ts +24 -0
  33. package/dist/events/ReelEvents.d.ts.map +1 -1
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +5 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +259 -114
  39. package/dist/index.js.map +1 -1
  40. package/dist/snapshot/StaticSpinSymbol.d.ts +2 -1
  41. package/dist/snapshot/StaticSpinSymbol.d.ts.map +1 -1
  42. package/dist/spin/SpinController.d.ts.map +1 -1
  43. package/dist/spin/phases/CascadeDropInPhase.d.ts.map +1 -1
  44. package/dist/spine/SpineReelSymbol.d.ts +24 -5
  45. package/dist/spine/SpineReelSymbol.d.ts.map +1 -1
  46. package/dist/spine/index.d.ts +2 -1
  47. package/dist/spine/index.d.ts.map +1 -1
  48. package/dist/spine.cjs +1 -1
  49. package/dist/spine.cjs.map +1 -1
  50. package/dist/spine.js +20 -9
  51. package/dist/spine.js.map +1 -1
  52. package/dist/symbols/ReelSymbol.d.ts +27 -2
  53. package/dist/symbols/ReelSymbol.d.ts.map +1 -1
  54. package/dist/testing.cjs +1 -1
  55. package/dist/testing.js +2 -2
  56. package/package.json +18 -8
  57. package/dist/ReelSymbol-D698guvK.js.map +0 -1
  58. package/dist/ReelSymbol-DjJzuBhn.cjs +0 -2
  59. package/dist/ReelSymbol-DjJzuBhn.cjs.map +0 -1
  60. package/dist/debug-B0vmxmQR.js.map +0 -1
  61. package/dist/debug-DkAoLN21.cjs +0 -4
  62. package/dist/debug-DkAoLN21.cjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # pixi-reels
2
2
 
3
+ ## 2.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#239](https://github.com/schmooky/pixi-reels/pull/239) [`64b3a0b`](https://github.com/schmooky/pixi-reels/commit/64b3a0bfc0536c67deb2a25181ee6bb5b465a428) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: the landing frame as a signal, and a say for the symbol in what it plays there.
8
+
9
+ - `spin:reelLanding` (`reelIndex, symbols`) fires the frame a reel is on its result: strip snapped, every visible symbol told it landed, the bounce not yet started. `spin:reelLanded` still fires when the reel is fully at rest, a whole `bounceDuration` later on an animated stop and in the same tick on a slam. The new event fires on every landing path (animated stop, slam, each cascade refill stage), always after the `onReelLanded()` loop, so a listener that takes a landed symbol's track over finds the engine's own landing already set. Each `Reel` raises the per-reel `landing` event it is bridged from.
10
+ - `ReelSymbol.onReelLanded(ctx)` now receives a `ReelLandingContext` - `reelIndex`, `reelCount`, `cell`, `visibleCells`, `symbolId` - so an override can play a different beat, or none, on a particular reel. Overrides written as `onReelLanded()` keep compiling.
11
+ - `SpineReelSymbol`'s `autoPlayLanding` accepts a function of that context returning `true` (the landing one-shot), `false` (nothing) or an animation name to play as the landing beat on this reel and cell instead. `StaticSpinSymbol` forwards the context to its inner symbol, so the rule works behind a baked spin blur too.
12
+ - `ReelSymbol.landing` exposes the landing beat a symbol started on land (reported by subclasses through the new `trackLanding(promise)`), kept until the reel moves again or the symbol is pooled, so a presenter can sequence after it instead of stomping it.
13
+ - `HoldAndWinBuilder.lockAnimation` uses it: `'win'` now waits for the coin's own landing beat before the celebration (before, a Spine coin's landing never showed under the default lock), `'landing'` no longer replays one the symbol already started, and the mode may be a function of the coin - `(coin) => coin.id === 'collector' ? 'win' : 'landing'` (`HwLockAnimationRule`).
14
+
15
+ - [#239](https://github.com/schmooky/pixi-reels/pull/239) [`64b3a0b`](https://github.com/schmooky/pixi-reels/commit/64b3a0bfc0536c67deb2a25181ee6bb5b465a428) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `inset(strategy, pixels)` takes a per-side trim as well as one number: `inset(new RoundedRectMaskStrategy({ radius: 43 }), { top: 4, bottom: 12 })`. The sides are screen sides in every orientation, an omitted side is untouched, and a negative one grows. Uneven cross-axis sides are split into their symmetric half (through `bleed`, as before) and a shift of the whole mask, so every built-in strategy honours them and curve bleed still composes. Replaces the hand-written `PathMaskStrategy` a game needed for a frame whose top and bottom lips differ.
16
+
17
+ - [#239](https://github.com/schmooky/pixi-reels/pull/239) [`64b3a0b`](https://github.com/schmooky/pixi-reels/commit/64b3a0bfc0536c67deb2a25181ee6bb5b465a428) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: consumer-owned symbol draw order. `ReelSetBuilder.symbolZIndex(resolver)` replaces the engine's `symbolData.zIndex * 100 + cellStackingIndex` with the resolver's answer, asked with a `SymbolZIndexContext` (`symbolId`, `symbolData`, `reelIndex`, `reelCount`, `arrayIndex`, `visibleCell`, `visibleCells`, `atRest`, `defaultZIndex`) and re-asked whenever a symbol's id, cell, reel shape or rest state changes - including after the at-rest unmask lift and the re-mask on departure, which the old refresh did not cover. Omitted, nothing changes. `HoldAndWinBuilder.symbolZIndex` and the `BoardGrid` option pass it to every cell. For the order BETWEEN cells of a board, whose lifted art shares one layer, `HoldAndWinBuilder.cellZIndex(resolver)` / the `BoardGrid` `cellZIndex` option make that layer sortable and ask for each cell's value on every place and landing (`BoardCellZIndexContext`: `symbolId`, `cell`, `cols`, `rows`, `atRest`, `attachOrder`); `liftedLayer` exposes the layer itself. `Z_INDEX_BUDGET` publishes the reserved values (`symbolLayer: 100`, `pinOverlay: 10000`) so a resolver can be bounds-checked against the library.
18
+
19
+ ### Patch Changes
20
+
21
+ - [#239](https://github.com/schmooky/pixi-reels/pull/239) [`64b3a0b`](https://github.com/schmooky/pixi-reels/commit/64b3a0bfc0536c67deb2a25181ee6bb5b465a428) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix: `pixi-reels/spine` and `pixi-reels/testing` resolve their types under `moduleResolution: node` (node10) too. That resolver ignores `exports`, so those subpaths had no declarations at all and a game on it had to declare the module by hand; `typesVersions` now maps them. The `types` condition is also listed first in every `exports` entry, as TypeScript asks.
22
+
23
+ ## 2.6.0
24
+
25
+ ### Minor Changes
26
+
27
+ - [#221](https://github.com/schmooky/pixi-reels/pull/221) [`c96cfdd`](https://github.com/schmooky/pixi-reels/commit/c96cfdd5a29ccbb783425bd4f35765b43fd49941) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `RoundedRectMaskStrategy` gains `scope: 'outer'` (one rect per reel, only the corners that sit on the set's bounding box rounded, safe at a zero cross gap) and a `corners` option (`{ topLeft, topRight, bottomLeft, bottomRight }`, screen-space) that limits which corners round in any scope. `HoldAndWinBuilder.cellMask` and the `BoardGrid` `mask` option now hand the factory `(cell, { cols, rows, corners })`, where `corners` are the board corners that cell sits on, so `(_, { corners }) => new RoundedRectMaskStrategy({ radius, corners })` clips a gapless board as one rounded window with a separate rect mask per cell. Zero-argument factories keep working.
28
+
29
+ ### Patch Changes
30
+
31
+ - [#220](https://github.com/schmooky/pixi-reels/pull/220) [`767453a`](https://github.com/schmooky/pixi-reels/commit/767453a4f32c1c84ae4a8f0589d9f6a93ba6d0b9) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Perf: `SpineReelSymbol` takes its cached, hidden Spine instances off the ticker. It keeps one instance per symbol id for instant swaps, and each of those was created with spine-pixi's default `autoUpdate`, so every parked skeleton kept updating its animation state and world transform every frame while invisible - hundreds of them on a Hold & Win board of 1x1 reels. Now only the instance on screen updates; a parked one is resumed the moment it is shown again.
32
+
3
33
  ## 2.5.0
4
34
 
5
35
  ### Minor Changes
@@ -25,6 +25,16 @@ var n = t, r = class {
25
25
  get symbolId() {
26
26
  return this._symbolId;
27
27
  }
28
+ _landing = null;
29
+ get landing() {
30
+ return this._landing;
31
+ }
32
+ trackLanding(e) {
33
+ return this._landing = e, e;
34
+ }
35
+ resetLanding() {
36
+ this._landing = null;
37
+ }
28
38
  get isDestroyed() {
29
39
  return this._isDestroyed;
30
40
  }
@@ -32,7 +42,7 @@ var n = t, r = class {
32
42
  this._symbolId = e, this.view.visible = !0, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0, this.onActivate(e);
33
43
  }
34
44
  deactivate() {
35
- this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this.view.visible = !1, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0;
45
+ this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this._landing = null, this.view.visible = !1, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0;
36
46
  }
37
47
  reset() {
38
48
  this.deactivate();
@@ -141,9 +151,9 @@ var n = t, r = class {
141
151
  onReelSpinStart(e) {}
142
152
  onReelSpinEnd() {}
143
153
  onReelAnticipationStart() {}
144
- onReelLanded() {}
154
+ onReelLanded(e) {}
145
155
  };
146
156
  //#endregion
147
157
  export { n, r as t };
148
158
 
149
- //# sourceMappingURL=ReelSymbol-D698guvK.js.map
159
+ //# sourceMappingURL=ReelSymbol-BvdqdXMY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-BvdqdXMY.js","names":[],"sources":["../src/utils/gsap.ts","../src/symbols/ReelSymbol.ts"],"sourcesContent":["import { gsap as resolvedGsap } from 'gsap';\n\n/** The gsap namespace type, as the engine passes it around. */\nexport type Gsap = typeof resolvedGsap;\n\n/**\n * The gsap instance resolved at lib-load time. Used by any `ReelSet` whose\n * builder did not call `.gsap(...)`, and by any symbol not yet bound to a\n * set.\n *\n * **Why the engine passes gsap around at all:** under tools that resolve\n * modules through symlinked workspaces (vite + a locally-linked pixi-reels,\n * pnpm dev setups, esbuild plugin chains), the gsap import inside the lib's\n * compiled `dist/index.js` and the gsap import in the consumer's source can\n * resolve to *different module instances*. each with its own root timeline.\n * The consumer drives one, the lib's tweens live on the other, and reels\n * stall at progress 0. `ReelSetBuilder.gsap(myGsap)` hands the engine the\n * consumer's instance so both live on the same timeline.\n *\n * Held PER REEL SET, not process-wide: two sets on one stage can be driven\n * by different gsap instances, which is what a composed stage (a banner reel\n * above a main grid, a bonus board beside it) needs.\n */\nexport const DEFAULT_GSAP: Gsap = resolvedGsap;\n","import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelCellInset, ReelCellQuad, ReelLandingContext } from '../config/types.js';\nimport { DEFAULT_GSAP, type Gsap } from '../utils/gsap.js';\n\n/**\n * One visible cell on a reel. the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)`. the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()`. I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()`. the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()`. spotlight is over, return to idle.\n * - `resize(w, h)`. the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n private _gsap: Gsap = DEFAULT_GSAP;\n private _mainAxis: 'x' | 'y' = 'y';\n\n constructor() {\n this.view = new Container();\n }\n\n /**\n * The gsap instance this symbol should animate on. Use it instead of\n * importing `gsap` in a subclass: under a symlinked-workspace module\n * resolution your import and the engine's can be different instances, and\n * only this one is on the timeline the reel set actually drives.\n *\n * Bound to the owning set by `SymbolFactory`; falls back to the instance\n * resolved at lib-load time for a symbol built outside a set.\n */\n protected get gsap(): Gsap {\n return this._gsap;\n }\n\n /**\n * @internal. Called by `SymbolFactory` when the symbol is created, so a\n * pooled symbol animates on its own set's gsap rather than whichever set\n * happened to build last.\n */\n bindGsap(instance: Gsap): void {\n this._gsap = instance;\n }\n\n /**\n * The screen axis the owning set's strips travel along: `'y'` for a\n * vertical set, `'x'` for a horizontal one.\n *\n * Symbols are otherwise orientation-agnostic - `resize(width, height)` is\n * screen-space and always will be. This exists for the few effects that\n * genuinely follow travel, motion blur being the one in the box.\n */\n protected get mainAxis(): 'x' | 'y' {\n return this._mainAxis;\n }\n\n /** @internal. Bound by `SymbolFactory` from the set's orientation. */\n bindMainAxis(prop: 'x' | 'y'): void {\n this._mainAxis = prop;\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n private _landing: Promise<void> | null = null;\n\n /**\n * The landing beat this symbol started on its latest `onReelLanded()`, or\n * `null` if it started none. Stays set (resolved) until the reel moves\n * again or the symbol is pooled, so a presenter that lands cells one at a\n * time - `HoldAndWinBoard` on a lock, a win spotlight - can sequence after\n * the landing (`await symbol.landing`) instead of stomping it, and can\n * tell a symbol that already landed from one that still needs\n * `playLanding()`.\n *\n * Subclasses report a landing through {@link trackLanding}.\n */\n get landing(): Promise<void> | null {\n return this._landing;\n }\n\n /**\n * Subclass helper: record `run` as this symbol's landing beat (see\n * {@link landing}) and return it. `SpineReelSymbol` wraps its landing\n * one-shot in it; a sprite symbol wraps its settle tween.\n */\n protected trackLanding(run: Promise<void>): Promise<void> {\n this._landing = run;\n return run;\n }\n\n /** @internal Called by the reel when it leaves rest; the landing is over. */\n resetLanding(): void {\n this._landing = null;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity. Called when the symbol enters\n * the visible reel or is recycled from the pool. Resets container\n * transform / filter state for parity with deactivate().\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n this.view.zIndex = 0;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool. Stops\n * animations, hides the view, and resets container transform / filter\n * state so subclass decorations don't leak across recycles.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this._landing = null;\n this.view.visible = false;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n this.view.zIndex = 0;\n }\n\n /** Pool reset. aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.stopAnimation();\n this.onDeactivate();\n this.onDestroy();\n if (!this.view.destroyed) this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {}\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /**\n * Play the landing beat for this symbol - the settle a symbol does as its\n * reel stops, before (or instead of) any celebration. Resolves when done.\n *\n * The reel engine never calls this itself: `onReelLanded()` is the\n * engine-driven hook, fired on every landed symbol. This is the explicit\n * counterpart for a caller that lands cells one at a time - `HoldAndWinBoard`\n * calls it on each lock when built with `lockAnimation('landing')`.\n *\n * Default: resolves at once. `SpineReelSymbol` overrides it with the\n * skeleton's `landing` track; a sprite symbol can override it with a tween.\n */\n async playLanding(): Promise<void> {}\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Play the cascade-destruction animation for this symbol. Called by\n * consumers (typically via `reelSet.destroySymbols(...)`) to disintegrate\n * a winning cell before the next cascade refill drops fresh symbols in.\n *\n * Override in subclasses for art-appropriate destruction, e.g. a Spine\n * symbol can play its `disintegration` track here, or a sprite symbol can\n * swap to a shatter atlas. The promise must resolve when the symbol is no\n * longer visible.\n *\n * Default: a snappy \"poof\" centered on the symbol's bounds regardless of\n * the view's anchor. Tiny anticipation pop (~60 ms) then a fast implode to\n * `scale: 0` + `alpha: 0` (~140 ms), ~200 ms total, no rotation. Reads\n * cleanly under win-cluster pacing without competing with the win\n * presenter. The view is left at `alpha: 0` (destroyed); position / pivot\n * are restored so pool reuse via `_replaceSymbol`'s same-id fast path\n * doesn't inherit a stale pivot offset.\n *\n * `opts.delay`. seconds to wait before the animation starts. Use to\n * stagger a cluster of winners (e.g. `i * 0.015`).\n * `opts.signal`. abort signal. If aborted (now or mid-animation), the\n * tween is killed and the view is snapped to its destroyed pose\n * (`alpha: 0`, transform restored). The promise resolves normally. abort\n * means \"skip to the end,\" not \"fail\". Subclasses that override this\n * method MUST honor the signal or document why they can't (e.g. a Spine\n * `disintegration` track is uninterruptible).\n */\n async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n const view = this.view;\n // Capture original transform so pool reuse sees a clean state.\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n // Pivot to bounds-center so the scale collapses around the visual\n // centre instead of the view's (0,0) corner. and compensate position\n // so the symbol doesn't visibly jump when the pivot moves.\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n // Moving the pivot moves the view by `delta * scale`, not by `delta`: a\n // container renders a local point at `position + (point - pivot) * scale`.\n // Scale is 1 on a plain reel, which is why dropping it went unnoticed, but\n // a curved reel scales every cell and the symbol would jump on destroy.\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n const snapDestroyed = (): void => {\n view.alpha = 0;\n view.scale.set(0, 0);\n };\n\n // Pre-abort: skip the tween entirely and snap to the destroyed pose.\n if (signal?.aborted) {\n snapDestroyed();\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n view.alpha = 0;\n return;\n }\n\n await new Promise<void>((resolve) => {\n const tl = this.gsap\n .timeline({ onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, delay })\n // Brief anticipation pop. small upscale, ~60 ms, with overshoot\n // so the implode reads as a release. No rotation.\n .to(view.scale, { x: 1.1, y: 1.1, duration: 0.06, ease: 'back.out(2.5)' })\n // Snap implode. scale -> 0 + alpha -> 0 together, snappy ease-in\n // so the symbol collapses into the cell centre and is gone.\n .to(view.scale, { x: 0, y: 0, duration: 0.14, ease: 'power3.in' }, '<+=0.04')\n .to(view, { alpha: 0, duration: 0.14, ease: 'power3.in' }, '<');\n\n const onAbort = (): void => {\n tl.kill();\n snapDestroyed();\n resolve();\n };\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Restore transform. alpha stays 0 (the symbol IS destroyed). Scale\n // restored to 1 so pool reuse via `_replaceSymbol`'s same-id fast path\n // doesn't inherit a stale 0× scale; _replaceSymbol also resets scale\n // explicitly but a defensive restore here makes the destroyed cell\n // observably \"ready to be re-skinned\" between calls.\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n }\n\n /**\n * Animate this symbol AWAY, without destroying it.\n *\n * The counterpart to {@link playIn}, and the seam a mystery reveal needs:\n * the cells on screen dissolve, their identities are swapped underneath, and\n * the new ones arrive. `playDestroy` is the cascade's version of the same\n * beat and is deliberately separate - it is tuned as a \"this cell was a\n * winner and is being consumed\" poof, and a reveal is not that.\n *\n * Leaves the view hidden (`alpha: 0`) with its transform restored, so the\n * caller may swap the identity and call `playIn` next.\n *\n * Override for art-appropriate exits - a Spine symbol plays its own `out`\n * track here. Honour `opts.signal`: abort means \"snap to the end\", not\n * \"fail\", so the promise still resolves.\n *\n * Default: a ~180 ms shrink-and-fade with no overshoot, centred on the\n * symbol's bounds rather than the view origin.\n */\n async playOut(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('out', opts);\n }\n\n /**\n * Animate this symbol IN, from nothing to its resting pose.\n *\n * Owns its own start pose. A symbol that has just been re-activated is fully\n * visible (`activate()` resets alpha and scale), so an entrance that assumed\n * it started hidden would pop before it animated. This sets the hidden pose\n * first, then plays.\n *\n * Override for art-appropriate entrances - a Spine symbol plays its own `in`\n * track here. Honour `opts.signal`: abort snaps to the RESTING pose (alpha 1,\n * scale 1), because arriving is what the caller asked for.\n *\n * Default: a ~200 ms fade and scale-up with a small overshoot.\n */\n async playIn(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('in', opts);\n }\n\n /**\n * Shared body of {@link playIn} / {@link playOut}.\n *\n * One function because the two are the same tween read in opposite\n * directions, and because the fiddly part - pivoting to the visual centre so\n * the scale does not collapse toward the view's (0, 0) corner, and\n * compensating the position for the pivot move - is identical and easy to get\n * subtly wrong twice. Same correction as `playDestroy`: a container renders a\n * local point at `position + (point - pivot) * scale`, so a curved reel (which\n * scales every cell) would make the symbol jump without the `* scale` term.\n */\n private async _tweenPresence(\n direction: 'in' | 'out',\n opts?: { delay?: number; signal?: AbortSignal },\n ): Promise<void> {\n const view = this.view;\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const restorePivot = (): void => {\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n };\n const snapEnd = (): void => {\n if (direction === 'out') {\n view.alpha = 0;\n view.scale.set(1, 1);\n } else {\n view.visible = true;\n view.alpha = 1;\n view.scale.set(1, 1);\n }\n };\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n if (direction === 'in') {\n // Own the start pose: `activate()` leaves a freshly swapped symbol fully\n // visible, so without this the new art is on screen for a frame before\n // the entrance begins.\n view.visible = true;\n view.alpha = 0;\n view.scale.set(0.72, 0.72);\n }\n\n if (signal?.aborted) {\n snapEnd();\n restorePivot();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => {\n tl.kill();\n snapEnd();\n resolve();\n };\n const tl = this.gsap.timeline({\n delay,\n onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n },\n });\n if (direction === 'out') {\n tl.to(view.scale, { x: 0.72, y: 0.72, duration: 0.18, ease: 'power2.in' }).to(\n view,\n { alpha: 0, duration: 0.18, ease: 'power2.in' },\n '<',\n );\n } else {\n tl.to(view.scale, { x: 1, y: 1, duration: 0.2, ease: 'back.out(1.7)' }).to(\n view,\n { alpha: 1, duration: 0.16, ease: 'power2.out' },\n '<',\n );\n }\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Scale back to 1 either way: a hidden symbol still has to be a legal\n // resting pose, or pool reuse inherits the shrunken scale.\n view.scale.set(1, 1);\n restorePivot();\n }\n\n /**\n * The owning reel is curved: render into this projected quad instead of the\n * flat cell rectangle. `null` means the reel is flat again.\n *\n * The quad's corners are SCREEN-space and local to this view's own origin,\n * clockwise from top-left, and `width` / `height` give the flat cell box the\n * quad replaces. Called on every frame of motion and on every placement, so\n * it must be cheap and idempotent.\n *\n * **The default is an approximation.** A `Container` transform is affine, so\n * it cannot express a trapezoid; the base class fits the quad as closely as\n * an affine transform can - a UNIFORM scale about the quad's centre. Uniform\n * on purpose: symbol art is usually smaller than its cell and has a shape a\n * player recognises, and squashing one axis turns a `7` into a squashed `7`\n * rather than a `7` seen at an angle.\n *\n * Override this to render the real perspective. {@link SpriteSymbol} and\n * {@link AnimatedSpriteSymbol} do, by drawing their texture through a\n * `PerspectiveMesh`, which costs no extra render pass because the content is\n * already a texture. A symbol whose content is an arbitrary subtree (Spine, a\n * composite of sprites and text) cannot do that without rendering itself to a\n * texture every frame, so it keeps the affine fit.\n *\n * Whatever you do here, do NOT move `view.position`: the reel reads that\n * coordinate back to work out which slot this symbol is in.\n */\n /**\n * The part of its cell this symbol's art actually covers, or `null` (the\n * default) for \"all of it\".\n *\n * Slot art is usually smaller than its cell - a trimmed atlas frame is a\n * shape floating in a much bigger transparent box - and the reel needs to\n * know that to project the rectangle the art is really in. Overriding this\n * is what stops a small symbol being inflated to the cell's edges and given\n * the cell's keystone instead of its own, milder one.\n *\n * Read once per projection, so it may change with the symbol's identity.\n */\n get cellInset(): ReelCellInset | null {\n return null;\n }\n\n applyCellQuad(quad: ReelCellQuad | null): void {\n const view = this.view;\n if (quad === null) {\n view.scale.set(1, 1);\n view.pivot.set(0, 0);\n return;\n }\n // Measure the trapezoid: the mean of its two parallel edges, and the\n // distance between their midpoints.\n const nearWidth = Math.hypot(quad.x1 - quad.x0, quad.y1 - quad.y0);\n const farWidth = Math.hypot(quad.x2 - quad.x3, quad.y2 - quad.y3);\n const across = (nearWidth + farWidth) / 2;\n const along = Math.hypot(\n (quad.x3 + quad.x2) / 2 - (quad.x0 + quad.x1) / 2,\n (quad.y3 + quad.y2) / 2 - (quad.y0 + quad.y1) / 2,\n );\n // CONTAIN, not cover. A quad in the middle of the window is TALLER than\n // the flat cell (that is the drum magnifying what faces you), so a scale\n // picked to fill it would also make the symbol wider than its column and\n // overlap its neighbours - very visible on art that fills its cell\n // edge-to-edge. Taking the smaller ratio keeps every symbol inside its own\n // projected footprint at the cost of a little slack on one axis.\n const scale =\n quad.width > 0 && quad.height > 0\n ? Math.min(across / quad.width, along / quad.height)\n : 1;\n\n const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n view.scale.set(scale, scale);\n // A container renders a local point at `position + (point - pivot) * scale`\n // and `position` must not move, so putting the flat box's centre on the\n // quad's centre has to be paid for entirely out of the pivot.\n view.pivot.set(\n quad.x + quad.width / 2 - cx / scale,\n quad.y + quad.height / 2 - cy / scale,\n );\n }\n\n /**\n * Lifecycle hook: the owning reel is spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur,\n * StaticSpinSymbol) to swap to a spin presentation automatically.\n *\n * Fired on every strip symbol (visible AND buffer cells) when the reel\n * enters the spin phase, and again with `joinedMidSpin: true` on each\n * symbol freshly installed while the reel is already spinning (pool\n * recycling wipes symbol state, so a wrapped-in symbol can't know the\n * reel is moving without this). Implementations MUST be idempotent.\n * the same instance can be notified more than once per spin.\n *\n * @param joinedMidSpin true when this symbol was installed into a reel\n * already at speed (skip start-of-spin transitions like blur ramps).\n */\n onReelSpinStart(joinedMidSpin?: boolean): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel entered its anticipation (tease) phase.\n * it is still spinning, but slowed enough that the strip is readable.\n * Spin presentations that obscure symbols (blur textures, smear\n * animations) should relax so the player can follow the tease. Also\n * fired on symbols installed while the reel is anticipating.\n * Implementations MUST be idempotent. Default: no-op.\n */\n onReelAnticipationStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n *\n * `ctx` says which reel and cell this symbol landed in, so an override can\n * play a different beat - or none - on a particular reel. The engine always\n * supplies it; it is optional only so an override written as\n * `onReelLanded()` keeps compiling. Fired before the reel's `landing` event\n * and the set's `spin:reelLanding`, by contract.\n */\n onReelLanded(ctx?: ReelLandingContext): void {}\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ let e=require(`pixi.js`);var t=require(`gsap`).gsap,n=class{view;_symbolId=``;_isDestroyed=!1;_gsap=t;_mainAxis=`y`;constructor(){this.view=new e.Container}get gsap(){return this._gsap}bindGsap(e){this._gsap=e}get mainAxis(){return this._mainAxis}bindMainAxis(e){this._mainAxis=e}get symbolId(){return this._symbolId}_landing=null;get landing(){return this._landing}trackLanding(e){return this._landing=e,e}resetLanding(){this._landing=null}get isDestroyed(){return this._isDestroyed}activate(e){this._symbolId=e,this.view.visible=!0,this.view.alpha=1,this.view.scale.set(1,1),this.view.rotation=0,this.view.filters=null,this.view.zIndex=0,this.onActivate(e)}deactivate(){this.stopAnimation(),this.onDeactivate(),this._symbolId=``,this._landing=null,this.view.visible=!1,this.view.alpha=1,this.view.scale.set(1,1),this.view.rotation=0,this.view.filters=null,this.view.zIndex=0}reset(){this.deactivate()}destroy(){this._isDestroyed||=(this.stopAnimation(),this.onDeactivate(),this.onDestroy(),this.view.destroyed||this.view.destroy({children:!0}),!0)}onDestroy(){}async playLanding(){}async playDestroy(e){let t=this.view,n=t.pivot.x,r=t.pivot.y,i=t.x,a=t.y,o=t.getLocalBounds(),s=o.x+o.width/2,c=o.y+o.height/2;t.pivot.set(s,c),t.x=i+(s-n)*t.scale.x,t.y=a+(c-r)*t.scale.y;let l=e?.delay??0,u=e?.signal,d=()=>{t.alpha=0,t.scale.set(0,0)};if(u?.aborted){d(),t.pivot.set(n,r),t.x=i,t.y=a,t.scale.set(1,1),t.alpha=0;return}await new Promise(e=>{let n=this.gsap.timeline({onComplete:()=>{u&&u.removeEventListener(`abort`,r),e()},delay:l}).to(t.scale,{x:1.1,y:1.1,duration:.06,ease:`back.out(2.5)`}).to(t.scale,{x:0,y:0,duration:.14,ease:`power3.in`},`<+=0.04`).to(t,{alpha:0,duration:.14,ease:`power3.in`},`<`),r=()=>{n.kill(),d(),e()};u&&u.addEventListener(`abort`,r,{once:!0})}),t.pivot.set(n,r),t.x=i,t.y=a,t.scale.set(1,1)}async playOut(e){await this._tweenPresence(`out`,e)}async playIn(e){await this._tweenPresence(`in`,e)}async _tweenPresence(e,t){let n=this.view,r=n.pivot.x,i=n.pivot.y,a=n.x,o=n.y,s=n.getLocalBounds(),c=s.x+s.width/2,l=s.y+s.height/2;n.pivot.set(c,l),n.x=a+(c-r)*n.scale.x,n.y=o+(l-i)*n.scale.y;let u=()=>{n.pivot.set(r,i),n.x=a,n.y=o},d=()=>{e===`out`?(n.alpha=0,n.scale.set(1,1)):(n.visible=!0,n.alpha=1,n.scale.set(1,1))},f=t?.delay??0,p=t?.signal;if(e===`in`&&(n.visible=!0,n.alpha=0,n.scale.set(.72,.72)),p?.aborted){d(),u();return}await new Promise(t=>{let r=()=>{i.kill(),d(),t()},i=this.gsap.timeline({delay:f,onComplete:()=>{p&&p.removeEventListener(`abort`,r),t()}});e===`out`?i.to(n.scale,{x:.72,y:.72,duration:.18,ease:`power2.in`}).to(n,{alpha:0,duration:.18,ease:`power2.in`},`<`):i.to(n.scale,{x:1,y:1,duration:.2,ease:`back.out(1.7)`}).to(n,{alpha:1,duration:.16,ease:`power2.out`},`<`),p&&p.addEventListener(`abort`,r,{once:!0})}),n.scale.set(1,1),u()}get cellInset(){return null}applyCellQuad(e){let t=this.view;if(e===null){t.scale.set(1,1),t.pivot.set(0,0);return}let n=(Math.hypot(e.x1-e.x0,e.y1-e.y0)+Math.hypot(e.x2-e.x3,e.y2-e.y3))/2,r=Math.hypot((e.x3+e.x2)/2-(e.x0+e.x1)/2,(e.y3+e.y2)/2-(e.y0+e.y1)/2),i=e.width>0&&e.height>0?Math.min(n/e.width,r/e.height):1,a=(e.x0+e.x1+e.x2+e.x3)/4,o=(e.y0+e.y1+e.y2+e.y3)/4;t.scale.set(i,i),t.pivot.set(e.x+e.width/2-a/i,e.y+e.height/2-o/i)}onReelSpinStart(e){}onReelSpinEnd(){}onReelAnticipationStart(){}onReelLanded(e){}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return n}});
2
+ //# sourceMappingURL=ReelSymbol-CTlZ9ihf.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-CTlZ9ihf.cjs","names":[],"sources":["../src/utils/gsap.ts","../src/symbols/ReelSymbol.ts"],"sourcesContent":["import { gsap as resolvedGsap } from 'gsap';\n\n/** The gsap namespace type, as the engine passes it around. */\nexport type Gsap = typeof resolvedGsap;\n\n/**\n * The gsap instance resolved at lib-load time. Used by any `ReelSet` whose\n * builder did not call `.gsap(...)`, and by any symbol not yet bound to a\n * set.\n *\n * **Why the engine passes gsap around at all:** under tools that resolve\n * modules through symlinked workspaces (vite + a locally-linked pixi-reels,\n * pnpm dev setups, esbuild plugin chains), the gsap import inside the lib's\n * compiled `dist/index.js` and the gsap import in the consumer's source can\n * resolve to *different module instances*. each with its own root timeline.\n * The consumer drives one, the lib's tweens live on the other, and reels\n * stall at progress 0. `ReelSetBuilder.gsap(myGsap)` hands the engine the\n * consumer's instance so both live on the same timeline.\n *\n * Held PER REEL SET, not process-wide: two sets on one stage can be driven\n * by different gsap instances, which is what a composed stage (a banner reel\n * above a main grid, a bonus board beside it) needs.\n */\nexport const DEFAULT_GSAP: Gsap = resolvedGsap;\n","import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelCellInset, ReelCellQuad, ReelLandingContext } from '../config/types.js';\nimport { DEFAULT_GSAP, type Gsap } from '../utils/gsap.js';\n\n/**\n * One visible cell on a reel. the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)`. the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()`. I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()`. the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()`. spotlight is over, return to idle.\n * - `resize(w, h)`. the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n private _gsap: Gsap = DEFAULT_GSAP;\n private _mainAxis: 'x' | 'y' = 'y';\n\n constructor() {\n this.view = new Container();\n }\n\n /**\n * The gsap instance this symbol should animate on. Use it instead of\n * importing `gsap` in a subclass: under a symlinked-workspace module\n * resolution your import and the engine's can be different instances, and\n * only this one is on the timeline the reel set actually drives.\n *\n * Bound to the owning set by `SymbolFactory`; falls back to the instance\n * resolved at lib-load time for a symbol built outside a set.\n */\n protected get gsap(): Gsap {\n return this._gsap;\n }\n\n /**\n * @internal. Called by `SymbolFactory` when the symbol is created, so a\n * pooled symbol animates on its own set's gsap rather than whichever set\n * happened to build last.\n */\n bindGsap(instance: Gsap): void {\n this._gsap = instance;\n }\n\n /**\n * The screen axis the owning set's strips travel along: `'y'` for a\n * vertical set, `'x'` for a horizontal one.\n *\n * Symbols are otherwise orientation-agnostic - `resize(width, height)` is\n * screen-space and always will be. This exists for the few effects that\n * genuinely follow travel, motion blur being the one in the box.\n */\n protected get mainAxis(): 'x' | 'y' {\n return this._mainAxis;\n }\n\n /** @internal. Bound by `SymbolFactory` from the set's orientation. */\n bindMainAxis(prop: 'x' | 'y'): void {\n this._mainAxis = prop;\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n private _landing: Promise<void> | null = null;\n\n /**\n * The landing beat this symbol started on its latest `onReelLanded()`, or\n * `null` if it started none. Stays set (resolved) until the reel moves\n * again or the symbol is pooled, so a presenter that lands cells one at a\n * time - `HoldAndWinBoard` on a lock, a win spotlight - can sequence after\n * the landing (`await symbol.landing`) instead of stomping it, and can\n * tell a symbol that already landed from one that still needs\n * `playLanding()`.\n *\n * Subclasses report a landing through {@link trackLanding}.\n */\n get landing(): Promise<void> | null {\n return this._landing;\n }\n\n /**\n * Subclass helper: record `run` as this symbol's landing beat (see\n * {@link landing}) and return it. `SpineReelSymbol` wraps its landing\n * one-shot in it; a sprite symbol wraps its settle tween.\n */\n protected trackLanding(run: Promise<void>): Promise<void> {\n this._landing = run;\n return run;\n }\n\n /** @internal Called by the reel when it leaves rest; the landing is over. */\n resetLanding(): void {\n this._landing = null;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity. Called when the symbol enters\n * the visible reel or is recycled from the pool. Resets container\n * transform / filter state for parity with deactivate().\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n this.view.zIndex = 0;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool. Stops\n * animations, hides the view, and resets container transform / filter\n * state so subclass decorations don't leak across recycles.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this._landing = null;\n this.view.visible = false;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n this.view.zIndex = 0;\n }\n\n /** Pool reset. aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.stopAnimation();\n this.onDeactivate();\n this.onDestroy();\n if (!this.view.destroyed) this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {}\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /**\n * Play the landing beat for this symbol - the settle a symbol does as its\n * reel stops, before (or instead of) any celebration. Resolves when done.\n *\n * The reel engine never calls this itself: `onReelLanded()` is the\n * engine-driven hook, fired on every landed symbol. This is the explicit\n * counterpart for a caller that lands cells one at a time - `HoldAndWinBoard`\n * calls it on each lock when built with `lockAnimation('landing')`.\n *\n * Default: resolves at once. `SpineReelSymbol` overrides it with the\n * skeleton's `landing` track; a sprite symbol can override it with a tween.\n */\n async playLanding(): Promise<void> {}\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Play the cascade-destruction animation for this symbol. Called by\n * consumers (typically via `reelSet.destroySymbols(...)`) to disintegrate\n * a winning cell before the next cascade refill drops fresh symbols in.\n *\n * Override in subclasses for art-appropriate destruction, e.g. a Spine\n * symbol can play its `disintegration` track here, or a sprite symbol can\n * swap to a shatter atlas. The promise must resolve when the symbol is no\n * longer visible.\n *\n * Default: a snappy \"poof\" centered on the symbol's bounds regardless of\n * the view's anchor. Tiny anticipation pop (~60 ms) then a fast implode to\n * `scale: 0` + `alpha: 0` (~140 ms), ~200 ms total, no rotation. Reads\n * cleanly under win-cluster pacing without competing with the win\n * presenter. The view is left at `alpha: 0` (destroyed); position / pivot\n * are restored so pool reuse via `_replaceSymbol`'s same-id fast path\n * doesn't inherit a stale pivot offset.\n *\n * `opts.delay`. seconds to wait before the animation starts. Use to\n * stagger a cluster of winners (e.g. `i * 0.015`).\n * `opts.signal`. abort signal. If aborted (now or mid-animation), the\n * tween is killed and the view is snapped to its destroyed pose\n * (`alpha: 0`, transform restored). The promise resolves normally. abort\n * means \"skip to the end,\" not \"fail\". Subclasses that override this\n * method MUST honor the signal or document why they can't (e.g. a Spine\n * `disintegration` track is uninterruptible).\n */\n async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n const view = this.view;\n // Capture original transform so pool reuse sees a clean state.\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n // Pivot to bounds-center so the scale collapses around the visual\n // centre instead of the view's (0,0) corner. and compensate position\n // so the symbol doesn't visibly jump when the pivot moves.\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n // Moving the pivot moves the view by `delta * scale`, not by `delta`: a\n // container renders a local point at `position + (point - pivot) * scale`.\n // Scale is 1 on a plain reel, which is why dropping it went unnoticed, but\n // a curved reel scales every cell and the symbol would jump on destroy.\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n const snapDestroyed = (): void => {\n view.alpha = 0;\n view.scale.set(0, 0);\n };\n\n // Pre-abort: skip the tween entirely and snap to the destroyed pose.\n if (signal?.aborted) {\n snapDestroyed();\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n view.alpha = 0;\n return;\n }\n\n await new Promise<void>((resolve) => {\n const tl = this.gsap\n .timeline({ onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, delay })\n // Brief anticipation pop. small upscale, ~60 ms, with overshoot\n // so the implode reads as a release. No rotation.\n .to(view.scale, { x: 1.1, y: 1.1, duration: 0.06, ease: 'back.out(2.5)' })\n // Snap implode. scale -> 0 + alpha -> 0 together, snappy ease-in\n // so the symbol collapses into the cell centre and is gone.\n .to(view.scale, { x: 0, y: 0, duration: 0.14, ease: 'power3.in' }, '<+=0.04')\n .to(view, { alpha: 0, duration: 0.14, ease: 'power3.in' }, '<');\n\n const onAbort = (): void => {\n tl.kill();\n snapDestroyed();\n resolve();\n };\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Restore transform. alpha stays 0 (the symbol IS destroyed). Scale\n // restored to 1 so pool reuse via `_replaceSymbol`'s same-id fast path\n // doesn't inherit a stale 0× scale; _replaceSymbol also resets scale\n // explicitly but a defensive restore here makes the destroyed cell\n // observably \"ready to be re-skinned\" between calls.\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n }\n\n /**\n * Animate this symbol AWAY, without destroying it.\n *\n * The counterpart to {@link playIn}, and the seam a mystery reveal needs:\n * the cells on screen dissolve, their identities are swapped underneath, and\n * the new ones arrive. `playDestroy` is the cascade's version of the same\n * beat and is deliberately separate - it is tuned as a \"this cell was a\n * winner and is being consumed\" poof, and a reveal is not that.\n *\n * Leaves the view hidden (`alpha: 0`) with its transform restored, so the\n * caller may swap the identity and call `playIn` next.\n *\n * Override for art-appropriate exits - a Spine symbol plays its own `out`\n * track here. Honour `opts.signal`: abort means \"snap to the end\", not\n * \"fail\", so the promise still resolves.\n *\n * Default: a ~180 ms shrink-and-fade with no overshoot, centred on the\n * symbol's bounds rather than the view origin.\n */\n async playOut(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('out', opts);\n }\n\n /**\n * Animate this symbol IN, from nothing to its resting pose.\n *\n * Owns its own start pose. A symbol that has just been re-activated is fully\n * visible (`activate()` resets alpha and scale), so an entrance that assumed\n * it started hidden would pop before it animated. This sets the hidden pose\n * first, then plays.\n *\n * Override for art-appropriate entrances - a Spine symbol plays its own `in`\n * track here. Honour `opts.signal`: abort snaps to the RESTING pose (alpha 1,\n * scale 1), because arriving is what the caller asked for.\n *\n * Default: a ~200 ms fade and scale-up with a small overshoot.\n */\n async playIn(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('in', opts);\n }\n\n /**\n * Shared body of {@link playIn} / {@link playOut}.\n *\n * One function because the two are the same tween read in opposite\n * directions, and because the fiddly part - pivoting to the visual centre so\n * the scale does not collapse toward the view's (0, 0) corner, and\n * compensating the position for the pivot move - is identical and easy to get\n * subtly wrong twice. Same correction as `playDestroy`: a container renders a\n * local point at `position + (point - pivot) * scale`, so a curved reel (which\n * scales every cell) would make the symbol jump without the `* scale` term.\n */\n private async _tweenPresence(\n direction: 'in' | 'out',\n opts?: { delay?: number; signal?: AbortSignal },\n ): Promise<void> {\n const view = this.view;\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const restorePivot = (): void => {\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n };\n const snapEnd = (): void => {\n if (direction === 'out') {\n view.alpha = 0;\n view.scale.set(1, 1);\n } else {\n view.visible = true;\n view.alpha = 1;\n view.scale.set(1, 1);\n }\n };\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n if (direction === 'in') {\n // Own the start pose: `activate()` leaves a freshly swapped symbol fully\n // visible, so without this the new art is on screen for a frame before\n // the entrance begins.\n view.visible = true;\n view.alpha = 0;\n view.scale.set(0.72, 0.72);\n }\n\n if (signal?.aborted) {\n snapEnd();\n restorePivot();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => {\n tl.kill();\n snapEnd();\n resolve();\n };\n const tl = this.gsap.timeline({\n delay,\n onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n },\n });\n if (direction === 'out') {\n tl.to(view.scale, { x: 0.72, y: 0.72, duration: 0.18, ease: 'power2.in' }).to(\n view,\n { alpha: 0, duration: 0.18, ease: 'power2.in' },\n '<',\n );\n } else {\n tl.to(view.scale, { x: 1, y: 1, duration: 0.2, ease: 'back.out(1.7)' }).to(\n view,\n { alpha: 1, duration: 0.16, ease: 'power2.out' },\n '<',\n );\n }\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Scale back to 1 either way: a hidden symbol still has to be a legal\n // resting pose, or pool reuse inherits the shrunken scale.\n view.scale.set(1, 1);\n restorePivot();\n }\n\n /**\n * The owning reel is curved: render into this projected quad instead of the\n * flat cell rectangle. `null` means the reel is flat again.\n *\n * The quad's corners are SCREEN-space and local to this view's own origin,\n * clockwise from top-left, and `width` / `height` give the flat cell box the\n * quad replaces. Called on every frame of motion and on every placement, so\n * it must be cheap and idempotent.\n *\n * **The default is an approximation.** A `Container` transform is affine, so\n * it cannot express a trapezoid; the base class fits the quad as closely as\n * an affine transform can - a UNIFORM scale about the quad's centre. Uniform\n * on purpose: symbol art is usually smaller than its cell and has a shape a\n * player recognises, and squashing one axis turns a `7` into a squashed `7`\n * rather than a `7` seen at an angle.\n *\n * Override this to render the real perspective. {@link SpriteSymbol} and\n * {@link AnimatedSpriteSymbol} do, by drawing their texture through a\n * `PerspectiveMesh`, which costs no extra render pass because the content is\n * already a texture. A symbol whose content is an arbitrary subtree (Spine, a\n * composite of sprites and text) cannot do that without rendering itself to a\n * texture every frame, so it keeps the affine fit.\n *\n * Whatever you do here, do NOT move `view.position`: the reel reads that\n * coordinate back to work out which slot this symbol is in.\n */\n /**\n * The part of its cell this symbol's art actually covers, or `null` (the\n * default) for \"all of it\".\n *\n * Slot art is usually smaller than its cell - a trimmed atlas frame is a\n * shape floating in a much bigger transparent box - and the reel needs to\n * know that to project the rectangle the art is really in. Overriding this\n * is what stops a small symbol being inflated to the cell's edges and given\n * the cell's keystone instead of its own, milder one.\n *\n * Read once per projection, so it may change with the symbol's identity.\n */\n get cellInset(): ReelCellInset | null {\n return null;\n }\n\n applyCellQuad(quad: ReelCellQuad | null): void {\n const view = this.view;\n if (quad === null) {\n view.scale.set(1, 1);\n view.pivot.set(0, 0);\n return;\n }\n // Measure the trapezoid: the mean of its two parallel edges, and the\n // distance between their midpoints.\n const nearWidth = Math.hypot(quad.x1 - quad.x0, quad.y1 - quad.y0);\n const farWidth = Math.hypot(quad.x2 - quad.x3, quad.y2 - quad.y3);\n const across = (nearWidth + farWidth) / 2;\n const along = Math.hypot(\n (quad.x3 + quad.x2) / 2 - (quad.x0 + quad.x1) / 2,\n (quad.y3 + quad.y2) / 2 - (quad.y0 + quad.y1) / 2,\n );\n // CONTAIN, not cover. A quad in the middle of the window is TALLER than\n // the flat cell (that is the drum magnifying what faces you), so a scale\n // picked to fill it would also make the symbol wider than its column and\n // overlap its neighbours - very visible on art that fills its cell\n // edge-to-edge. Taking the smaller ratio keeps every symbol inside its own\n // projected footprint at the cost of a little slack on one axis.\n const scale =\n quad.width > 0 && quad.height > 0\n ? Math.min(across / quad.width, along / quad.height)\n : 1;\n\n const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n view.scale.set(scale, scale);\n // A container renders a local point at `position + (point - pivot) * scale`\n // and `position` must not move, so putting the flat box's centre on the\n // quad's centre has to be paid for entirely out of the pivot.\n view.pivot.set(\n quad.x + quad.width / 2 - cx / scale,\n quad.y + quad.height / 2 - cy / scale,\n );\n }\n\n /**\n * Lifecycle hook: the owning reel is spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur,\n * StaticSpinSymbol) to swap to a spin presentation automatically.\n *\n * Fired on every strip symbol (visible AND buffer cells) when the reel\n * enters the spin phase, and again with `joinedMidSpin: true` on each\n * symbol freshly installed while the reel is already spinning (pool\n * recycling wipes symbol state, so a wrapped-in symbol can't know the\n * reel is moving without this). Implementations MUST be idempotent.\n * the same instance can be notified more than once per spin.\n *\n * @param joinedMidSpin true when this symbol was installed into a reel\n * already at speed (skip start-of-spin transitions like blur ramps).\n */\n onReelSpinStart(joinedMidSpin?: boolean): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel entered its anticipation (tease) phase.\n * it is still spinning, but slowed enough that the strip is readable.\n * Spin presentations that obscure symbols (blur textures, smear\n * animations) should relax so the player can follow the tease. Also\n * fired on symbols installed while the reel is anticipating.\n * Implementations MUST be idempotent. Default: no-op.\n */\n onReelAnticipationStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n *\n * `ctx` says which reel and cell this symbol landed in, so an override can\n * play a different beat - or none - on a particular reel. The engine always\n * supplies it; it is optional only so an override written as\n * `onReelLanded()` keeps compiling. Fired before the reel's `landing` event\n * and the set's `spin:reelLanding`, by contract.\n */\n onReelLanded(ctx?: ReelLandingContext): void {}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./ReelSymbol-D698guvK.js";
1
+ import { t as e } from "./ReelSymbol-BvdqdXMY.js";
2
2
  //#region src/symbols/SpineSymbol.ts
3
3
  var t = null, n = (async () => {
4
4
  try {
@@ -67,4 +67,4 @@ var i = class extends e {
67
67
  //#endregion
68
68
  export { r as n, i as t };
69
69
 
70
- //# sourceMappingURL=SpineSymbol-BL7iDOZj.js.map
70
+ //# sourceMappingURL=SpineSymbol-C6FYNcwP.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-BL7iDOZj.js","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SpineSymbol-C6FYNcwP.js","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./ReelSymbol-DjJzuBhn.cjs`);var t=null,n=(async()=>{try{t=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}})();function r(){return n}var i=class extends e.t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;_strict;constructor(e){if(super(),!t)throw Error(`SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is not installed (npm install @esotericsoftware/spine-pixi-v8), or its dynamic import has not resolved yet on a cold start — await whenSpineReady() before constructing SpineSymbols.`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`,this._strict=e.strict??!1}onActivate(e){let n=this._skeletonDataMap[e];if(n){if(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new t({skeletonData:n}),this.view.addChild(this._spine),this._currentSkeletonKey=e),this._spine.skeleton.data.findSkin(this._defaultSkin)&&(this._spine.skeleton.setSkinByName(this._defaultSkin),this._spine.skeleton.setSlotsToSetupPose()),this._spine.skeleton.data.findAnimation(this._idleAnimation))this._spine.state.setAnimation(0,this._idleAnimation,!0);else if(this._strict)throw Error(`SpineSymbol(strict): idle animation '${this._idleAnimation}' not found on skeleton '${e}'.`)}}onDeactivate(){if(this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}async playWin(){if(this._spine){if(!this._spine.skeleton.data.findAnimation(this._winAnimation)){if(this._strict)throw Error(`SpineSymbol(strict): win animation '${this._winAnimation}' not found on skeleton '${this._currentSkeletonKey}'.`);return}return new Promise(e=>{this._winResolve=e;let t=this._spine.state.setAnimation(0,this._winAnimation,!1);this._spine.state.addListener({complete:n=>{n===t&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve=null,e())}})})}}stopAnimation(){this._spine&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve&&=(this._winResolve(),null))}resize(e,t){if(!this._spine)return;let n=this._spine.getBounds();n.width>0&&n.height>0&&this._spine.scale.set(e/n.width,t/n.height)}onDestroy(){if(this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
2
- //# sourceMappingURL=SpineSymbol-Dq3IAu_k.cjs.map
1
+ const e=require(`./ReelSymbol-CTlZ9ihf.cjs`);var t=null,n=(async()=>{try{t=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}})();function r(){return n}var i=class extends e.t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;_strict;constructor(e){if(super(),!t)throw Error(`SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is not installed (npm install @esotericsoftware/spine-pixi-v8), or its dynamic import has not resolved yet on a cold start — await whenSpineReady() before constructing SpineSymbols.`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`,this._strict=e.strict??!1}onActivate(e){let n=this._skeletonDataMap[e];if(n){if(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new t({skeletonData:n}),this.view.addChild(this._spine),this._currentSkeletonKey=e),this._spine.skeleton.data.findSkin(this._defaultSkin)&&(this._spine.skeleton.setSkinByName(this._defaultSkin),this._spine.skeleton.setSlotsToSetupPose()),this._spine.skeleton.data.findAnimation(this._idleAnimation))this._spine.state.setAnimation(0,this._idleAnimation,!0);else if(this._strict)throw Error(`SpineSymbol(strict): idle animation '${this._idleAnimation}' not found on skeleton '${e}'.`)}}onDeactivate(){if(this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}async playWin(){if(this._spine){if(!this._spine.skeleton.data.findAnimation(this._winAnimation)){if(this._strict)throw Error(`SpineSymbol(strict): win animation '${this._winAnimation}' not found on skeleton '${this._currentSkeletonKey}'.`);return}return new Promise(e=>{this._winResolve=e;let t=this._spine.state.setAnimation(0,this._winAnimation,!1);this._spine.state.addListener({complete:n=>{n===t&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve=null,e())}})})}}stopAnimation(){this._spine&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve&&=(this._winResolve(),null))}resize(e,t){if(!this._spine)return;let n=this._spine.getBounds();n.width>0&&n.height>0&&this._spine.scale.set(e/n.width,t/n.height)}onDestroy(){if(this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
2
+ //# sourceMappingURL=SpineSymbol-Do0zw5ad.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-Dq3IAu_k.cjs","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"6CAGA,IAAI,EAAkB,KAMhB,GAA6B,SAAY,CAC7C,GAAI,CAEF,GADoB,MAAM,OAAO,oCACR,WACnB,MAGN,CAQJ,SAAgB,GAAgC,CAC9C,OAAO,EA2BT,IAAa,EAAb,cAAiC,EAAA,CAAW,CAC1C,OAAsB,KACtB,iBACA,eACA,cACA,aACA,YAA2C,KAC3C,oBAAsC,GACtC,QAEA,YAAY,EAA6B,CAEvC,GADA,OAAO,CACH,CAAC,EACH,MAAU,MACR,6PAID,CAEH,KAAK,iBAAmB,EAAQ,gBAChC,KAAK,eAAiB,EAAQ,eAAiB,OAC/C,KAAK,cAAgB,EAAQ,cAAgB,MAC7C,KAAK,aAAe,EAAQ,aAAe,UAC3C,KAAK,QAAU,EAAQ,QAAU,GAGnC,WAAqB,EAAwB,CAC3C,IAAM,EAAe,KAAK,iBAAiB,GACtC,KAiBL,IAdI,KAAK,sBAAwB,IAC3B,KAAK,SACP,KAAK,KAAK,YAAY,KAAK,OAAO,CAClC,KAAK,OAAO,SAAS,EAEvB,KAAK,OAAS,IAAI,EAAW,CAAE,eAAc,CAAC,CAC9C,KAAK,KAAK,SAAS,KAAK,OAAO,CAC/B,KAAK,oBAAsB,GAGzB,KAAK,OAAO,SAAS,KAAK,SAAS,KAAK,aAAa,GACvD,KAAK,OAAO,SAAS,cAAc,KAAK,aAAa,CACrD,KAAK,OAAO,SAAS,qBAAqB,EAExC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,CAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,SACnD,KAAK,QACd,MAAU,MACR,wCAAwC,KAAK,eAAe,2BAC5C,EAAS,IAC1B,EAIL,cAA+B,CAO7B,GANI,KAAK,SACP,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,MAAM,aAAa,EAI7B,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG,EAIP,MAAM,SAAyB,CACxB,QAAK,OACV,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,CAAE,CAChE,GAAI,KAAK,QACP,MAAU,MACR,uCAAuC,KAAK,cAAc,2BAC1C,KAAK,oBAAoB,IAC1C,CAEH,OAGF,OAAO,IAAI,QAAe,GAAY,CACpC,KAAK,YAAc,EACnB,IAAM,EAAQ,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,cAAe,GAAM,CAC1E,KAAK,OAAO,MAAM,YAAY,CAC5B,SAAW,GAAoB,CACzB,IAAe,IACjB,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,KAAK,YAAc,KACnB,GAAS,GAGd,CAAC,EACF,EAGJ,eAAsB,CACf,KAAK,SACV,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,AAEE,KAAK,eADL,KAAK,aAAa,CACC,OAIvB,OAAO,EAAe,EAAsB,CAC1C,GAAI,CAAC,KAAK,OAAQ,OAClB,IAAM,EAAS,KAAK,OAAO,WAAW,CAClC,EAAO,MAAQ,GAAK,EAAO,OAAS,GACtC,KAAK,OAAO,MAAM,IAChB,EAAQ,EAAO,MACf,EAAS,EAAO,OACjB,CAIL,WAAqC,CAMnC,GALA,AAGE,KAAK,UAFL,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,SAAS,CACP,MAEZ,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG"}
1
+ {"version":3,"file":"SpineSymbol-Do0zw5ad.cjs","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"6CAGA,IAAI,EAAkB,KAMhB,GAA6B,SAAY,CAC7C,GAAI,CAEF,GADoB,MAAM,OAAO,oCACR,WACnB,MAGN,CAQJ,SAAgB,GAAgC,CAC9C,OAAO,EA2BT,IAAa,EAAb,cAAiC,EAAA,CAAW,CAC1C,OAAsB,KACtB,iBACA,eACA,cACA,aACA,YAA2C,KAC3C,oBAAsC,GACtC,QAEA,YAAY,EAA6B,CAEvC,GADA,OAAO,CACH,CAAC,EACH,MAAU,MACR,6PAID,CAEH,KAAK,iBAAmB,EAAQ,gBAChC,KAAK,eAAiB,EAAQ,eAAiB,OAC/C,KAAK,cAAgB,EAAQ,cAAgB,MAC7C,KAAK,aAAe,EAAQ,aAAe,UAC3C,KAAK,QAAU,EAAQ,QAAU,GAGnC,WAAqB,EAAwB,CAC3C,IAAM,EAAe,KAAK,iBAAiB,GACtC,KAiBL,IAdI,KAAK,sBAAwB,IAC3B,KAAK,SACP,KAAK,KAAK,YAAY,KAAK,OAAO,CAClC,KAAK,OAAO,SAAS,EAEvB,KAAK,OAAS,IAAI,EAAW,CAAE,eAAc,CAAC,CAC9C,KAAK,KAAK,SAAS,KAAK,OAAO,CAC/B,KAAK,oBAAsB,GAGzB,KAAK,OAAO,SAAS,KAAK,SAAS,KAAK,aAAa,GACvD,KAAK,OAAO,SAAS,cAAc,KAAK,aAAa,CACrD,KAAK,OAAO,SAAS,qBAAqB,EAExC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,CAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,SACnD,KAAK,QACd,MAAU,MACR,wCAAwC,KAAK,eAAe,2BAC5C,EAAS,IAC1B,EAIL,cAA+B,CAO7B,GANI,KAAK,SACP,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,MAAM,aAAa,EAI7B,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG,EAIP,MAAM,SAAyB,CACxB,QAAK,OACV,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,CAAE,CAChE,GAAI,KAAK,QACP,MAAU,MACR,uCAAuC,KAAK,cAAc,2BAC1C,KAAK,oBAAoB,IAC1C,CAEH,OAGF,OAAO,IAAI,QAAe,GAAY,CACpC,KAAK,YAAc,EACnB,IAAM,EAAQ,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,cAAe,GAAM,CAC1E,KAAK,OAAO,MAAM,YAAY,CAC5B,SAAW,GAAoB,CACzB,IAAe,IACjB,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,KAAK,YAAc,KACnB,GAAS,GAGd,CAAC,EACF,EAGJ,eAAsB,CACf,KAAK,SACV,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,AAEE,KAAK,eADL,KAAK,aAAa,CACC,OAIvB,OAAO,EAAe,EAAsB,CAC1C,GAAI,CAAC,KAAK,OAAQ,OAClB,IAAM,EAAS,KAAK,OAAO,WAAW,CAClC,EAAO,MAAQ,GAAK,EAAO,OAAS,GACtC,KAAK,OAAO,MAAM,IAChB,EAAQ,EAAO,MACf,EAAS,EAAO,OACjB,CAIL,WAAqC,CAMnC,GALA,AAGE,KAAK,UAFL,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,SAAS,CACP,MAEZ,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG"}
@@ -1,10 +1,11 @@
1
- import { Container, Graphics, Ticker } from 'pixi.js';
1
+ import { Container, Graphics, RenderLayer, Ticker } from 'pixi.js';
2
2
  import { Direction, Orientation } from '../core/ReelAxis.js';
3
3
  import { ReelSet } from '../core/ReelSet.js';
4
4
  import { MaskStrategy } from '../core/ReelViewport.js';
5
+ import { MaskCorners } from '../core/maskStrategies.js';
5
6
  import { ReelSymbol } from '../symbols/ReelSymbol.js';
6
7
  import { SymbolRegistry } from '../symbols/SymbolRegistry.js';
7
- import { SpeedProfile, SymbolData } from '../config/types.js';
8
+ import { SpeedProfile, SymbolData, SymbolZIndexResolver } from '../config/types.js';
8
9
  import { Disposable } from '../utils/Disposable.js';
9
10
  /** A cell coordinate in the grid. */
10
11
  export interface BoardCell {
@@ -16,8 +17,38 @@ export interface BoardSpinTarget {
16
17
  cell: BoardCell;
17
18
  id: string;
18
19
  }
20
+ /** What a cell-mask factory is told about the cell it builds for. */
21
+ export interface BoardCellMaskInfo {
22
+ cols: number;
23
+ rows: number;
24
+ /**
25
+ * The BOARD corners this cell sits on: `{ topLeft: true }` and the rest
26
+ * `false` for cell `(0, 0)`, all `false` for an inner cell. Hand it to
27
+ * `RoundedRectMaskStrategy` as `corners` and the board reads as one rounded
28
+ * window built from one rect per cell.
29
+ */
30
+ corners: MaskCorners;
31
+ }
19
32
  /** A speed profile, or a per-cell function of one (e.g. a stagger wave). */
20
33
  export type BoardProfile = SpeedProfile | ((cell: BoardCell) => SpeedProfile);
34
+ /** What a {@link BoardCellZIndexResolver} is asked about. */
35
+ export interface BoardCellZIndexContext {
36
+ /** Id currently shown in the cell. */
37
+ symbolId: string;
38
+ /** The board coordinate - what a per-symbol resolver cannot supply, since every cell is reel 0 of its own set. */
39
+ cell: BoardCell;
40
+ cols: number;
41
+ rows: number;
42
+ /** `false` while the cell's own strip is in flight. */
43
+ atRest: boolean;
44
+ /** The cell's attach index (column-major); returning it reproduces the default order. */
45
+ attachOrder: number;
46
+ }
47
+ /**
48
+ * Orders the cells' lifted art. See the `cellZIndex` option of
49
+ * {@link BoardGridOptions}.
50
+ */
51
+ export type BoardCellZIndexResolver = (ctx: BoardCellZIndexContext) => number;
21
52
  export interface BoardGridOptions {
22
53
  /** Grid dimensions. */
23
54
  cols: number;
@@ -55,9 +86,12 @@ export interface BoardGridOptions {
55
86
  * Mask for each cell, built once per cell (every cell is its own reel set
56
87
  * and owns its mask). Default: a shared rect over the cell. Hand it
57
88
  * `() => new RoundedRectMaskStrategy({ radius })` for cells whose art and
58
- * frame have rounded corners.
89
+ * frame have rounded corners. The factory is told which cell it builds for
90
+ * and which board corners that cell sits on, so
91
+ * `(_, { corners }) => new RoundedRectMaskStrategy({ radius, corners })`
92
+ * rounds only the board's outer corners and keeps every other cell square.
59
93
  */
60
- mask?: () => MaskStrategy;
94
+ mask?: (cell: BoardCell, info: BoardCellMaskInfo) => MaskStrategy;
61
95
  /**
62
96
  * Which way each cell's own strip travels while it spins. Every cell is a
63
97
  * 1x1 reel set, so this changes the direction a symbol scrolls in from, not
@@ -74,6 +108,22 @@ export interface BoardGridOptions {
74
108
  * profile, which is the active one until you `setProfile` otherwise.
75
109
  */
76
110
  profiles?: Record<string, BoardProfile>;
111
+ /**
112
+ * Per-symbol z-index resolver, exactly like `ReelSetBuilder.symbolZIndex`,
113
+ * applied to every cell's reel set. Orders symbols INSIDE a cell; for the
114
+ * order between cells see `cellZIndex`.
115
+ */
116
+ symbolZIndex?: SymbolZIndexResolver;
117
+ /**
118
+ * Draw order of the cells' lifted art - the `unmask: true` symbols the
119
+ * engine lifts above each cell's mask at rest, which the grid renders in one
120
+ * shared layer above every cell. Without this the layer draws in attach
121
+ * order (column-major), so a lower cell's overflowing art is covered by the
122
+ * next column's. Provided, the layer sorts by the value returned for each
123
+ * cell, re-asked whenever any cell's symbol changes (place, landing).
124
+ * `ctx.attachOrder` reproduces the default.
125
+ */
126
+ cellZIndex?: BoardCellZIndexResolver;
77
127
  }
78
128
  /**
79
129
  * A grid of cells that each spin **independently** - the generic "board of
@@ -123,8 +173,23 @@ export declare class BoardGrid implements Disposable {
123
173
  * the neighbour's edge.
124
174
  */
125
175
  private readonly _lifted;
176
+ private readonly _cellZIndex;
126
177
  private _destroyed;
127
178
  constructor(opts: BoardGridOptions);
179
+ /**
180
+ * The layer every cell's lifted (`unmask: true`, at rest) art renders in,
181
+ * above all cells. Escape hatch for an effect that must sit between the
182
+ * cells and their lifted art, or above both; the `cellZIndex` option is the
183
+ * supported way to order the cells themselves.
184
+ */
185
+ get liftedLayer(): RenderLayer;
186
+ /**
187
+ * Re-ask the `cellZIndex` resolver for every cell and write the answers
188
+ * onto the cells' lifted containers. Called on every `place` and every
189
+ * cell landing; a no-op without a resolver. Fifteen property writes on a
190
+ * 5x3 board, so the contract is unconditional rather than incremental.
191
+ */
192
+ refreshCellZIndex(): void;
128
193
  /**
129
194
  * Cell edge length. Only meaningful on a square board; a rectangular board
130
195
  * reports its width here.
@@ -1 +1 @@
1
- {"version":3,"file":"BoardGrid.d.ts","sourceRoot":"","sources":["../../src/board/BoardGrid.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BoardGrid.d.ts","sourceRoot":"","sources":["../../src/board/BoardGrid.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
- import { Container, Graphics, Ticker } from 'pixi.js';
1
+ import { Container, Graphics, Ticker, RenderLayer } from 'pixi.js';
2
2
  import { EventEmitter } from '../events/EventEmitter.js';
3
3
  import { ReelSet } from '../core/ReelSet.js';
4
4
  import { ReelSymbol } from '../symbols/ReelSymbol.js';
5
5
  import { SymbolRegistry } from '../symbols/SymbolRegistry.js';
6
- import { SpeedProfile, SymbolData } from '../config/types.js';
6
+ import { SpeedProfile, SymbolData, SymbolZIndexResolver } from '../config/types.js';
7
7
  import { Disposable } from '../utils/Disposable.js';
8
+ import { BoardCellMaskInfo, BoardCellZIndexResolver } from './BoardGrid.js';
8
9
  import { Direction, Orientation } from '../core/ReelAxis.js';
9
10
  import { MaskStrategy } from '../core/ReelViewport.js';
10
11
  import { HwPhase } from './HoldAndWinState.js';
11
- import { HoldAndWinBoardEvents, HwCell, HwCoin, HwLockAnimation, HwRespinResult } from './HwTypes.js';
12
+ import { HoldAndWinBoardEvents, HwCell, HwCoin, HwRespinResult, HwLockAnimationRule } from './HwTypes.js';
12
13
  /** Internal config produced by {@link HoldAndWinBuilder.build}. */
13
14
  export interface HoldAndWinBoardConfig<TData> {
14
15
  cols: number;
@@ -23,7 +24,11 @@ export interface HoldAndWinBoardConfig<TData> {
23
24
  /** Symbol shown on a dormant cell. */
24
25
  inactiveId: string;
25
26
  respins: number;
26
- lockAnimation: HwLockAnimation;
27
+ lockAnimation: HwLockAnimationRule<TData>;
28
+ /** Per-symbol z-index resolver for every cell. See `HoldAndWinBuilder.symbolZIndex`. */
29
+ symbolZIndex: SymbolZIndexResolver | null;
30
+ /** Order of the cells' lifted art. See `HoldAndWinBuilder.cellZIndex`. */
31
+ cellZIndex: BoardCellZIndexResolver | null;
27
32
  configurator: (registry: SymbolRegistry) => void;
28
33
  weights: Record<string, number> | null;
29
34
  symbolData: Record<string, Partial<SymbolData>> | null;
@@ -38,7 +43,7 @@ export interface HoldAndWinBoardConfig<TData> {
38
43
  }) => boolean) | null;
39
44
  chrome: ((g: Graphics, width: number, height: number) => void) | null;
40
45
  /** Per-cell mask factory. See `HoldAndWinBuilder.cellMask`. */
41
- mask: (() => MaskStrategy) | null;
46
+ mask: ((cell: HwCell, info: BoardCellMaskInfo) => MaskStrategy) | null;
42
47
  /** Travel axis for each cell's own strip. See `HoldAndWinBuilder.axis`. */
43
48
  orientation?: Orientation;
44
49
  direction?: Direction;
@@ -96,6 +101,8 @@ export declare class HoldAndWinBoard<TData = unknown> implements Disposable {
96
101
  private _tenseWave;
97
102
  constructor(cfg: HoldAndWinBoardConfig<TData>);
98
103
  get container(): Container;
104
+ /** The layer the cells' lifted art renders in. See `BoardGrid.liftedLayer`. */
105
+ get liftedLayer(): RenderLayer;
99
106
  /** Number of active cells - what `isFull` is measured against. */
100
107
  get capacity(): number;
101
108
  get respinsLeft(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"HoldAndWinBoard.d.ts","sourceRoot":"","sources":["../../src/board/HoldAndWinBoard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"HoldAndWinBoard.d.ts","sourceRoot":"","sources":["../../src/board/HoldAndWinBoard.ts"],"names":[],"mappings":"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"}