pixi-reels 2.2.1 → 2.4.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 (64) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/ReelSymbol-BG8mBj2i.cjs +2 -0
  3. package/dist/ReelSymbol-BG8mBj2i.cjs.map +1 -0
  4. package/dist/{ReelSymbol-CRXD3IO3.js → ReelSymbol-JASIxGy4.js} +49 -1
  5. package/dist/ReelSymbol-JASIxGy4.js.map +1 -0
  6. package/dist/{SpineSymbol-Fe0Fz1JW.cjs → SpineSymbol-C4hNDBWZ.cjs} +2 -2
  7. package/dist/{SpineSymbol-Fe0Fz1JW.cjs.map → SpineSymbol-C4hNDBWZ.cjs.map} +1 -1
  8. package/dist/{SpineSymbol-BApOkJM6.js → SpineSymbol-tPguh5gN.js} +2 -2
  9. package/dist/{SpineSymbol-BApOkJM6.js.map → SpineSymbol-tPguh5gN.js.map} +1 -1
  10. package/dist/board/HoldAndWinBoard.d.ts.map +1 -1
  11. package/dist/config/types.d.ts +150 -0
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/core/Reel.d.ts +60 -0
  14. package/dist/core/Reel.d.ts.map +1 -1
  15. package/dist/core/ReelDrive.d.ts +120 -0
  16. package/dist/core/ReelDrive.d.ts.map +1 -0
  17. package/dist/core/ReelMotion.d.ts +15 -0
  18. package/dist/core/ReelMotion.d.ts.map +1 -1
  19. package/dist/core/ReelSet.d.ts +230 -6
  20. package/dist/core/ReelSet.d.ts.map +1 -1
  21. package/dist/core/ReelSetBuilder.d.ts +64 -3
  22. package/dist/core/ReelSetBuilder.d.ts.map +1 -1
  23. package/dist/core/ReelViewport.d.ts +25 -2
  24. package/dist/core/ReelViewport.d.ts.map +1 -1
  25. package/dist/core/maskStrategies.d.ts +241 -0
  26. package/dist/core/maskStrategies.d.ts.map +1 -0
  27. package/dist/debug-BjevIlUZ.cjs +4 -0
  28. package/dist/debug-BjevIlUZ.cjs.map +1 -0
  29. package/dist/{debug-Cq4DpNOO.js → debug-D_mYc6sW.js} +812 -288
  30. package/dist/debug-D_mYc6sW.js.map +1 -0
  31. package/dist/events/ReelEvents.d.ts +39 -2
  32. package/dist/events/ReelEvents.d.ts.map +1 -1
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.ts +18 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +296 -51
  38. package/dist/index.js.map +1 -1
  39. package/dist/spin/SpinController.d.ts +277 -13
  40. package/dist/spin/SpinController.d.ts.map +1 -1
  41. package/dist/spin/phases/AnticipationPhase.d.ts +89 -5
  42. package/dist/spin/phases/AnticipationPhase.d.ts.map +1 -1
  43. package/dist/spin/phases/StartPhase.d.ts +7 -0
  44. package/dist/spin/phases/StartPhase.d.ts.map +1 -1
  45. package/dist/spin/phases/StopPhase.d.ts.map +1 -1
  46. package/dist/spine.cjs +1 -1
  47. package/dist/spine.js +2 -2
  48. package/dist/symbols/ReelSymbol.d.ts +53 -0
  49. package/dist/symbols/ReelSymbol.d.ts.map +1 -1
  50. package/dist/testing/testHarness.d.ts +5 -0
  51. package/dist/testing/testHarness.d.ts.map +1 -1
  52. package/dist/testing.cjs +1 -1
  53. package/dist/testing.cjs.map +1 -1
  54. package/dist/testing.js +3 -3
  55. package/dist/testing.js.map +1 -1
  56. package/dist/utils/notify.d.ts +44 -0
  57. package/dist/utils/notify.d.ts.map +1 -0
  58. package/package.json +2 -2
  59. package/dist/ReelSymbol-0kVAZr4S.cjs +0 -2
  60. package/dist/ReelSymbol-0kVAZr4S.cjs.map +0 -1
  61. package/dist/ReelSymbol-CRXD3IO3.js.map +0 -1
  62. package/dist/debug-BN4AH9DY.cjs +0 -4
  63. package/dist/debug-BN4AH9DY.cjs.map +0 -1
  64. package/dist/debug-Cq4DpNOO.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,183 @@
1
1
  # pixi-reels
2
2
 
3
+ ## 2.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#215](https://github.com/schmooky/pixi-reels/pull/215) [`002ded2`](https://github.com/schmooky/pixi-reels/commit/002ded279aaf106d6f21050a81108ec30cfb683b) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix and sharpen shaped anticipation, following review of the feature above.
8
+
9
+ **Fix: a `curve` no longer scrolls a cascade reel.** A tumble reel has already
10
+ dropped its visible symbols and must tease at rest; the guard that pinned this
11
+ only covered the legacy tease, so a `curve` dragged buffer symbols back through
12
+ the empty window. `curve` / `cells` are now dropped in cascade mode with a
13
+ notice naming the reel.
14
+
15
+ **Fix: a travel anchor no longer deletes the legs before the last one.**
16
+ `cells` measured from the start of the tease, so a fast opening segment could
17
+ reach the target before the segments after it ever played — silently. The anchor
18
+ now applies to the final leg, which is what the docs always described.
19
+
20
+ **Fix: curve segments are validated at the call.** A negative `speed`, a
21
+ non-positive `duration`, a negative `hold` or a `NaN` were all accepted and
22
+ played. The function form is now resolved (and validated) for every teasing reel
23
+ when `setAnticipation` is called, so a bad curve throws next to the caller's own
24
+ stack instead of being swallowed by the reel task.
25
+
26
+ **Fix: drive bounds are profile-relative.** `motionModel('drive', { accelFrames:
27
+ 20 })` means "reach the ACTIVE profile's full spin speed in 20 frames" and
28
+ re-resolves per spin. The absolute `accel` form only suited a single-profile
29
+ game: with `spinSpeed` 30 / 50 / 80 across the presets, one fixed bound made
30
+ SuperTurbo take 53 frames to reach speed where Normal took 20. Mixing the two
31
+ forms throws. A drive that cannot meet a segment's time budget now says so.
32
+
33
+ **Fix: `composeMasks` no longer accumulates scene nodes.** A member that owns
34
+ its own Graphics — including any strategy wrapped in `inset(...)` — added a
35
+ fresh child on every redraw, so each viewport resize and MultiWays reshape
36
+ leaked a node.
37
+
38
+ **Fix: the new mask warnings go through the notice channel**, so they carry a
39
+ code and obey `setLogLevel('silent')` like every other notice.
40
+
41
+ Add: `anticipation:segment` fires once per curve leg (`{ reelIndex, index,
42
+ total, speed, targetSpeed }`), so tease audio can hit the surge and the crawl
43
+ separately instead of polling the speed to find the boundary. `cells` takes the
44
+ same function-of-tease-order form as `curve`. `stepDrive` writes into the state
45
+ it is given rather than allocating one per reel per frame, and a parked drive is
46
+ no longer stepped at all.
47
+
48
+ - [#215](https://github.com/schmooky/pixi-reels/pull/215) [`002ded2`](https://github.com/schmooky/pixi-reels/commit/002ded279aaf106d6f21050a81108ec30cfb683b) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `reelSet.setReelGroups([[0, 1], [2, 3], [4]])` — reels stop and skip as
49
+ blocks instead of individually.
50
+
51
+ Reel index was the engine's only ordering, which breaks as soon as a reel's job
52
+ is not tied to its neighbours. A filler reel meant to outlast a tease on the
53
+ reels before it landed in the middle of that tease instead, because its flat
54
+ `reelIndex * stopDelay` offset came due while they were still teasing, and a
55
+ skip press landed "everything outside the tease" — including that filler reel —
56
+ in one go.
57
+
58
+ A group is a barrier in both directions. **Stopping:** no reel in a group starts
59
+ its stop sequence (anticipation included) until every reel in the earlier groups
60
+ has landed, and a reel waiting its turn keeps spinning at full speed, so the
61
+ wait reads as "still going" rather than as a pause. **Skipping:** a press
62
+ releases the next un-landed group, with tease protection still applying inside
63
+ it — `protect: 'stepwise'` brings a group of teasing reels down one press at a
64
+ time, in tease order.
65
+
66
+ Stop delays become group-relative, so the profile's `stopDelay` staggers reels
67
+ within a group rather than re-adding a whole-board offset on top of the barrier.
68
+ An explicit `setStopDelays()` is still taken as given. Every reel must be listed
69
+ exactly once; `null` clears. Sticky across spins, like `setStopDelays()`.
70
+
71
+ Sets that never call it are unaffected.
72
+
73
+ - [#215](https://github.com/schmooky/pixi-reels/pull/215) [`002ded2`](https://github.com/schmooky/pixi-reels/commit/002ded279aaf106d6f21050a81108ec30cfb683b) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: mask primitives beyond the rectangle, and anticipation you can shape.
74
+
75
+ **Masks.** `RoundedRectMaskStrategy` rounds the whole grid (`scope: 'set'`) or
76
+ each reel as its own card (`scope: 'reel'`). `SilhouetteMaskStrategy` rounds the
77
+ outline of a jagged pyramid / MultiWays set — every step of the staircase,
78
+ concave corners included, with their own radius — instead of forcing you to pick
79
+ between notched seams and a bounding box that hides the shape.
80
+ `PathMaskStrategy` takes a `(graphics, context) => void` so a one-off custom
81
+ mask no longer needs a class. `inset(strategy, px)` shrinks any strategy's
82
+ output; `composeMasks(...)` unions several into one mask.
83
+
84
+ **Fix:** `RectMaskStrategy` ignored `ctx.bleed`, so a warped set combining
85
+ `curveBleed(...)` with an explicit `.maskStrategy(new RectMaskStrategy())` clipped
86
+ the very overhang the bleed asked for.
87
+
88
+ **Anticipation.** `setAnticipation(reels, { curve })` replaces the fixed
89
+ decelerate-then-hold with explicit speed legs, so a tease can surge above spin
90
+ speed before it crawls, and its transitions ramp instead of stepping (segment
91
+ eases default to `power2.inOut`). Pass a function of tease order to vary the
92
+ curve per reel. `{ cells: n }` ends a tease after N symbols of travel instead of
93
+ after a fixed time. `reel.speedNormalized` exposes live speed as a fraction of
94
+ spin speed, for tease audio that tracks the slow-down rather than just its
95
+ start and end.
96
+
97
+ **`motionModel('drive', { accel, decel, jerk })`** opts a set into
98
+ acceleration-bounded motion: phases set a target speed and the reel integrates
99
+ toward it, so every transition is shaped by the bounds instead of by a
100
+ per-transition ease, and a mid-move retarget stays continuous. Opt-in; the
101
+ default `'tween'` model is unchanged.
102
+
103
+ Existing spins are byte-for-byte unaffected: the new eases and the drive apply
104
+ only where you ask for them.
105
+
106
+ - [#215](https://github.com/schmooky/pixi-reels/pull/215) [`002ded2`](https://github.com/schmooky/pixi-reels/commit/002ded279aaf106d6f21050a81108ec30cfb683b) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `ReelSymbol.playIn()` / `playOut()`, and `reelSet.swapSymbols(...)` — the
107
+ mystery-reveal and upgrade beat as one call.
108
+
109
+ `setSymbolAt` already swapped an identity, but instantly. A game that wants "the
110
+ cells dissolve, the symbol underneath changes, the reveal arrives" had to
111
+ hand-roll the ordering, the stagger, the zIndex bump so an overshooting entrance
112
+ is not clipped, the re-hide after the swap (re-activation resets the view to
113
+ fully visible, so the new art popped for a frame before its entrance began), and
114
+ the abort handling — every time.
115
+
116
+ `playIn` / `playOut` are the symbol-level hooks, with the same contract as
117
+ `playDestroy`: `delay`, `signal`, resolve when done, abort means "snap to the
118
+ end" rather than "fail". Defaults are a short scale-and-fade; override them for
119
+ a Spine `in` / `out` track. They are separate from `playDestroy`, which stays
120
+ tuned as the cascade's "this cell was a winner and is being consumed" poof.
121
+
122
+ `swapSymbols(cells, opts)` orchestrates the three beats — out, swap, in — with
123
+ per-cell `outDelay` / `inDelay` staggers, a `holdMs` and an `onSwapped` hook for
124
+ the beat while the board is dark, and `skipOut` / `skipIn` for art that drives
125
+ one side itself. Cells are validated up front, and an abort still performs the
126
+ swap, so the board never disagrees with the result the server sent.
127
+
128
+ Single-cell symbols only: a big symbol spans cells the frame layer has to
129
+ reserve, so revealing one remains a `setResult` / `setShape` job.
130
+
131
+ ### Patch Changes
132
+
133
+ - [#215](https://github.com/schmooky/pixi-reels/pull/215) [`002ded2`](https://github.com/schmooky/pixi-reels/commit/002ded279aaf106d6f21050a81108ec30cfb683b) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - `setReelGroups()` now documents and enforces its window. A layout may be set any
134
+ time up to `setResult()` — including between `spin()` and `setResult()`, so a
135
+ round can be grouped from its own server response; the barrier is read as each
136
+ reel's SpinPhase resolves, which is exactly when the result lands. Changing the
137
+ layout once reels have begun landing throws instead of half-applying: a reel that
138
+ already passed the barrier cannot un-pass it, so the new layout would apply to
139
+ some reels and not others, silently.
140
+
141
+ ## 2.3.0
142
+
143
+ ### Minor Changes
144
+
145
+ - [#213](https://github.com/schmooky/pixi-reels/pull/213) [`600ad7d`](https://github.com/schmooky/pixi-reels/commit/600ad7db888ec00cddea17a51dad9b2de3733ca1) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: one console channel for everything the library says. Every warning and error now carries a stable CODE you can grep for, looks the same in the console, and obeys one volume knob via the new `setLogLevel(level)` / `getLogLevel()` (`'silent' | 'error' | 'warn' | 'info'`, default `'info'`). Before this, ten call sites hand-rolled their own `[pixi-reels] ...` string - one had no prefix at all - and there was no way to quieten them in a production build.
146
+
147
+ In a browser each notice prints as a styled badge (`pixi-reels` pill, then the code, then the message); everywhere else it degrades to `[pixi-reels] warn(code) message`, because `%c` is a browser console feature and Node prints the directives literally. Notices keep going through `console.warn` / `console.error` / `console.info` rather than a single `console.log`, so devtools filtering, stack capture and the browser's own warn/error styling all keep working, and detail arguments are passed through untouched so an `Error` keeps its stack.
148
+
149
+ Fix: `slamStop()` called before `setResult()` now says so. There is nothing to land on in that window, so the reels stop wherever the strip happens to be - random buffer fill in standard mode, and the alpha-0 residue of the fall-out in cascade mode, i.e. an invisible board. Nothing reported it; the reels just sat there showing the wrong thing. It stays a warning rather than a throw because `slamStop()` is the unconditional exit the engine's own abort, timeout and error-recovery paths depend on, and those legitimately fire before a result - `skipSpin()` is the guarded entry point and still throws in this window.
150
+
151
+ The default level is `'info'` rather than `'warn'` on purpose: the mask-strategy auto-pick notices this replaced were unconditional, and anything lower would have silently deleted advice the engine used to give.
152
+
153
+ - [#213](https://github.com/schmooky/pixi-reels/pull/213) [`600ad7d`](https://github.com/schmooky/pixi-reels/commit/600ad7db888ec00cddea17a51dad9b2de3733ca1) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: skip granularity. Skip used to be all-or-nothing. `skipSpin()` / `requestSkip()` / `slamStop()` force-completed every reel's phase, `AnticipationPhase` included, so a press on a teasing spin ended the tease before the player ever saw it. Three levers now open that up, plus the phase classes needed to build your own.
154
+
155
+ **`setAnticipation(reels, { protect })`** guarantees the tease becomes visible before a press can end it. `'once'` (or `true`) makes the first press of the round land every NON-tease reel immediately and leave the tease reels running, so the trigger symbols are on screen and the build-up is under way; the next press ends it. `'stepwise'` then releases the NEXT tease reel on each press after that, in tease order (tease order, not reel index), so the player walks the tension forward one reel at a time and the press that releases the last one is the round-ending press. `'always'` never lets a press end a tease. For any other grouping, pair `'always'` with your own `slamStop({ reels })` per press: protection keeps a press from ending a tease, and game code decides which group each press lands. `skipStage` reports `1` in that in-between state, which is what a UI should keep the button live on - gate on `isSpinning` first, since the stage is round-scoped and only resets on the next `spin()`, so an `'always'` round (which never reaches `2`) ends parked at `1`. Protection applies to `skipSpin()` and `requestSkip()` (including a press queued before `setResult()` arrived - call `setAnticipation` BEFORE `setResult` so the queued press can see the tease). Bare `slamStop()` stays an unconditional land-now. Protection is inert when the effective hold is `0` ms, as in Turbo / SuperTurbo without a `duration` override: there is no tease to protect, so every press lands everything.
156
+
157
+ **`slamStop({ reels })` / `slamStop({ except })`** is a per-reel slam. Those reels land now and every other reel keeps running its phase chain to a natural landing. It is the raw lever under `protect`, exposed so a game can express its own rule. A partial slam does not touch `skipStage` and does not end the round; a partial slam with nothing left to land is a no-op rather than a skip.
158
+
159
+ **`setMinimumSpinTime(ms | ms[])`** overrides the `SpinPhase` floor per reel. `minimumSpinTime` lives on the speed profile, so it is one value shared by every reel, and `setStopDelays()` - the only other per-reel lever - cannot go under it. The two missed each other: instant was only ever global, and per-reel could not go below the floor. Persists across `spin()` / `refill()` until cleared with `null`, matching `setStopDelays()`.
160
+
161
+ There is a fairness reason to prefer `protect` over raising the floor on teasing spins. If a scatterless skip lands instantly but a teasing skip settles at the floor, the response time itself tells the player a feature is coming before the reels have landed. `protect` keeps the non-tease reels landing at the same instant either way and puts the tell on screen where it belongs.
162
+
163
+ `skip:requested` and `skip:completed` now carry `{ reels, partial }` so a listener can tell a partial slam from a round-ending one. Listeners written against the old zero-argument signature keep working.
164
+
165
+ **The built-in phase classes are exported**: `StartPhase`, `SpinPhase`, `StopPhase`, `AnticipationPhase`, `AdjustPhase`, `CascadeFallPhase`, `CascadePlacePhase`, `CascadeDropInPhase`. `SpinPhaseConfig.minimumSpinTime` documented an override that could not be reached, because registering a phase through `PhaseFactory` meant reimplementing it rather than subclassing it. Now `class MyStop extends StopPhase` and register it. `resolveTumbleConfig` is exported alongside them: the three cascade phases take build-time config as extra constructor arguments, and a subclass registered through `registerFactory` has to forward the same resolved shape `.tumble()` would have passed. These are engine internals: the protected surface (`onEnter` / `onSkip` / `update` and each phase's private staging) can shift in a minor release, so a subclass may need to follow. The config TYPES remain the stable part.
166
+
167
+ Fix: a `.phases(...)` override of a cascade or MultiWays phase was silently discarded. `phases()` applied its configurator at call time, while `.tumble()` and `.multiways()` register their defaults later, inside `build()`, so any `'cascade:fall'` / `'cascade:place'` / `'cascade:dropIn'` / `'adjust'` registration was overwritten with no error - and the builder's own doc comment advised calling `.phases(...)` after `.tumble(...)`, which could not help, because chain position was never what decided the winner. Configurators are now deferred to the end of `build()`'s phase wiring, so an override wins from anywhere in the chain and the last override of a key wins.
168
+
169
+ `spin:allStarted` is now announced from a single place rather than only by a reel entering SPIN, so a partial slam that lands every reel still waiting to start no longer swallows it. It still fires at most once per round.
170
+
171
+ Internally, a partial slam cannot use the spin generation as its abort switch - that is global, and bumping it would strand the surviving reels mid-chain - so slammed indices are tracked per reel and each chain checks them at its own await boundaries. A full slam behaves exactly as before.
172
+
173
+ ### Patch Changes
174
+
175
+ - [#213](https://github.com/schmooky/pixi-reels/pull/213) [`600ad7d`](https://github.com/schmooky/pixi-reels/commit/600ad7db888ec00cddea17a51dad9b2de3733ca1) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix: a reel that teased during a tumble spin never came to rest. `AnticipationPhase` tweens `reel.speed` UP, and the tumble stop path never brings it back down - `cascade:place` swaps symbol identities and `cascade:dropIn` tweens views, and neither touches `reel.speed` the way `StopPhase._landAndBounce` does. So in any cascade game calling `setAnticipation()`, every teasing reel was left running at the tease speed after the round ended, drifting further off-grid every frame for the rest of the session while the untouched reels stayed put.
176
+
177
+ Two changes. A tumble tease is now a pure hold: the reel has already dropped its visible symbols and is sitting at zero, so scrolling it would drag buffer symbols back through the empty window, and the multiplier is pinned to `0` there whatever the `slowdown` curve says. And a tumble reel is brought to rest and snapped to the grid before the place phase, which holds the invariant even when a custom `'anticipation'` phase is registered and does move the reel.
178
+
179
+ Strip spins are unaffected: `StopPhase` was always resting the reel there, which is why this only ever showed up in cascades.
180
+
3
181
  ## 2.2.1
4
182
 
5
183
  ### Patch Changes
@@ -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}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.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 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(){}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return n}});
2
+ //# sourceMappingURL=ReelSymbol-BG8mBj2i.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-BG8mBj2i.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 } 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 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.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 /** 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 onReelLanded(): void {}\n}\n"],"mappings":"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"}
@@ -77,6 +77,54 @@ var n = t, r = class {
77
77
  u && u.addEventListener("abort", r, { once: !0 });
78
78
  }), t.pivot.set(n, r), t.x = i, t.y = a, t.scale.set(1, 1);
79
79
  }
80
+ async playOut(e) {
81
+ await this._tweenPresence("out", e);
82
+ }
83
+ async playIn(e) {
84
+ await this._tweenPresence("in", e);
85
+ }
86
+ async _tweenPresence(e, t) {
87
+ 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;
88
+ n.pivot.set(c, l), n.x = a + (c - r) * n.scale.x, n.y = o + (l - i) * n.scale.y;
89
+ let u = () => {
90
+ n.pivot.set(r, i), n.x = a, n.y = o;
91
+ }, d = () => {
92
+ e === "out" ? (n.alpha = 0, n.scale.set(1, 1)) : (n.visible = !0, n.alpha = 1, n.scale.set(1, 1));
93
+ }, f = t?.delay ?? 0, p = t?.signal;
94
+ if (e === "in" && (n.visible = !0, n.alpha = 0, n.scale.set(.72, .72)), p?.aborted) {
95
+ d(), u();
96
+ return;
97
+ }
98
+ await new Promise((t) => {
99
+ let r = () => {
100
+ i.kill(), d(), t();
101
+ }, i = this.gsap.timeline({
102
+ delay: f,
103
+ onComplete: () => {
104
+ p && p.removeEventListener("abort", r), t();
105
+ }
106
+ });
107
+ e === "out" ? i.to(n.scale, {
108
+ x: .72,
109
+ y: .72,
110
+ duration: .18,
111
+ ease: "power2.in"
112
+ }).to(n, {
113
+ alpha: 0,
114
+ duration: .18,
115
+ ease: "power2.in"
116
+ }, "<") : i.to(n.scale, {
117
+ x: 1,
118
+ y: 1,
119
+ duration: .2,
120
+ ease: "back.out(1.7)"
121
+ }).to(n, {
122
+ alpha: 1,
123
+ duration: .16,
124
+ ease: "power2.out"
125
+ }, "<"), p && p.addEventListener("abort", r, { once: !0 });
126
+ }), n.scale.set(1, 1), u();
127
+ }
80
128
  get cellInset() {
81
129
  return null;
82
130
  }
@@ -97,4 +145,4 @@ var n = t, r = class {
97
145
  //#endregion
98
146
  export { n, r as t };
99
147
 
100
- //# sourceMappingURL=ReelSymbol-CRXD3IO3.js.map
148
+ //# sourceMappingURL=ReelSymbol-JASIxGy4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-JASIxGy4.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 } 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 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.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 /** 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 onReelLanded(): void {}\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./ReelSymbol-0kVAZr4S.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-Fe0Fz1JW.cjs.map
1
+ const e=require(`./ReelSymbol-BG8mBj2i.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-C4hNDBWZ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-Fe0Fz1JW.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-C4hNDBWZ.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,4 +1,4 @@
1
- import { t as e } from "./ReelSymbol-CRXD3IO3.js";
1
+ import { t as e } from "./ReelSymbol-JASIxGy4.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-BApOkJM6.js.map
70
+ //# sourceMappingURL=SpineSymbol-tPguh5gN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-BApOkJM6.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-tPguh5gN.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 +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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"}