pixi-reels 2.7.0 → 3.0.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 (90) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/ReelSymbol-B5QBhkmw.cjs +2 -0
  3. package/dist/ReelSymbol-B5QBhkmw.cjs.map +1 -0
  4. package/dist/{ReelSymbol-BvdqdXMY.js → ReelSymbol-C7Pd7BeO.js} +4 -4
  5. package/dist/ReelSymbol-C7Pd7BeO.js.map +1 -0
  6. package/dist/{SpineSymbol-C6FYNcwP.js → SpineSymbol-CSTDCL4W.js} +2 -2
  7. package/dist/{SpineSymbol-C6FYNcwP.js.map → SpineSymbol-CSTDCL4W.js.map} +1 -1
  8. package/dist/{SpineSymbol-Do0zw5ad.cjs → SpineSymbol-DorL_xII.cjs} +2 -2
  9. package/dist/{SpineSymbol-Do0zw5ad.cjs.map → SpineSymbol-DorL_xII.cjs.map} +1 -1
  10. package/dist/board/BoardGrid.d.ts +215 -3
  11. package/dist/board/BoardGrid.d.ts.map +1 -1
  12. package/dist/board/HoldAndWinBoard.d.ts +58 -7
  13. package/dist/board/HoldAndWinBoard.d.ts.map +1 -1
  14. package/dist/board/HoldAndWinBuilder.d.ts +9 -1
  15. package/dist/board/HoldAndWinBuilder.d.ts.map +1 -1
  16. package/dist/board/HwTypes.d.ts +11 -6
  17. package/dist/board/HwTypes.d.ts.map +1 -1
  18. package/dist/config/types.d.ts +46 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/core/Reel.d.ts +50 -0
  21. package/dist/core/Reel.d.ts.map +1 -1
  22. package/dist/core/ReelSet.d.ts +72 -9
  23. package/dist/core/ReelSet.d.ts.map +1 -1
  24. package/dist/core/ReelSetBuilder.d.ts +9 -1
  25. package/dist/core/ReelSetBuilder.d.ts.map +1 -1
  26. package/dist/core/ReelViewport.d.ts +11 -1
  27. package/dist/core/ReelViewport.d.ts.map +1 -1
  28. package/dist/debug/debug.d.ts.map +1 -1
  29. package/dist/debug-Cou5DWoL.cjs +4 -0
  30. package/dist/debug-Cou5DWoL.cjs.map +1 -0
  31. package/dist/{debug-DJF_mzcM.js → debug-DnrD1PHb.js} +1018 -613
  32. package/dist/debug-DnrD1PHb.js.map +1 -0
  33. package/dist/events/EventEmitter.d.ts +8 -0
  34. package/dist/events/EventEmitter.d.ts.map +1 -1
  35. package/dist/events/ReelEvents.d.ts +70 -18
  36. package/dist/events/ReelEvents.d.ts.map +1 -1
  37. package/dist/index.cjs +1 -1
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.ts +13 -8
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +408 -129
  42. package/dist/index.js.map +1 -1
  43. package/dist/spin/SpinController.d.ts +98 -32
  44. package/dist/spin/SpinController.d.ts.map +1 -1
  45. package/dist/spin/phases/AdjustPhase.d.ts +9 -7
  46. package/dist/spin/phases/AdjustPhase.d.ts.map +1 -1
  47. package/dist/spin/phases/AnticipationPhase.d.ts +45 -18
  48. package/dist/spin/phases/AnticipationPhase.d.ts.map +1 -1
  49. package/dist/spin/phases/CascadeDropInPhase.d.ts +22 -10
  50. package/dist/spin/phases/CascadeDropInPhase.d.ts.map +1 -1
  51. package/dist/spin/phases/CascadeFallPhase.d.ts +15 -13
  52. package/dist/spin/phases/CascadeFallPhase.d.ts.map +1 -1
  53. package/dist/spin/phases/CascadePlacePhase.d.ts +8 -7
  54. package/dist/spin/phases/CascadePlacePhase.d.ts.map +1 -1
  55. package/dist/spin/phases/PhaseFactory.d.ts +19 -5
  56. package/dist/spin/phases/PhaseFactory.d.ts.map +1 -1
  57. package/dist/spin/phases/ReelPhase.d.ts +193 -10
  58. package/dist/spin/phases/ReelPhase.d.ts.map +1 -1
  59. package/dist/spin/phases/SpinPhase.d.ts +10 -4
  60. package/dist/spin/phases/SpinPhase.d.ts.map +1 -1
  61. package/dist/spin/phases/StartPhase.d.ts +28 -11
  62. package/dist/spin/phases/StartPhase.d.ts.map +1 -1
  63. package/dist/spin/phases/StopPhase.d.ts +37 -19
  64. package/dist/spin/phases/StopPhase.d.ts.map +1 -1
  65. package/dist/spin/phases/steps.d.ts +88 -0
  66. package/dist/spin/phases/steps.d.ts.map +1 -0
  67. package/dist/spine.cjs +1 -1
  68. package/dist/spine.cjs.map +1 -1
  69. package/dist/spine.js +3 -3
  70. package/dist/spine.js.map +1 -1
  71. package/dist/symbols/CardSymbol.d.ts +1 -1
  72. package/dist/symbols/CardSymbol.d.ts.map +1 -1
  73. package/dist/symbols/PhaseCardSymbol.d.ts +85 -0
  74. package/dist/symbols/PhaseCardSymbol.d.ts.map +1 -0
  75. package/dist/symbols/ReelSymbol.d.ts.map +1 -1
  76. package/dist/testing/testHarness.d.ts +2 -0
  77. package/dist/testing/testHarness.d.ts.map +1 -1
  78. package/dist/testing.cjs +1 -1
  79. package/dist/testing.cjs.map +1 -1
  80. package/dist/testing.js +3 -3
  81. package/dist/testing.js.map +1 -1
  82. package/dist/utils/notify.d.ts +15 -0
  83. package/dist/utils/notify.d.ts.map +1 -1
  84. package/package.json +6 -6
  85. package/dist/ReelSymbol-BvdqdXMY.js.map +0 -1
  86. package/dist/ReelSymbol-CTlZ9ihf.cjs +0 -2
  87. package/dist/ReelSymbol-CTlZ9ihf.cjs.map +0 -1
  88. package/dist/debug-BzQL0_Eq.cjs +0 -4
  89. package/dist/debug-BzQL0_Eq.cjs.map +0 -1
  90. package/dist/debug-DJF_mzcM.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,70 @@
1
1
  # pixi-reels
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#255](https://github.com/schmooky/pixi-reels/pull/255) [`1d9bab9`](https://github.com/schmooky/pixi-reels/commit/1d9bab908348d7c2faa7d3940c6dddd13a4cb43b) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: skip modes (`requestSkip({ mode: 'slam' | 'quicken' })`), `onSkip(ctx)` on every phase, the built-in phases as editable step lists, the press on the skip events and the results, and every built-in phase internal as `protected`. Remove: the unpublished hurry API. Breaking for anyone with a custom phase or a listener on the skip events; additive for everyone else.
8
+
9
+ **Skip modes.** `requestSkip(options?)` and `skipSpin(options?)` take `{ mode, speed, payload }`. `mode: 'slam'` (the default) places each freed reel on its result now, as every press always has. `mode: 'quicken'` asks each freed reel for its landing sooner without changing what the landing looks like: a tease ends, a stop delay is cut, the spin-out and bounce still play, and `speed` names a registered profile to finish on (the turbo bounce for a pressed reel, say). Which reels a press frees (tease protection, `'stepwise'`, reel groups) is the same in both modes; a quicken press treats the reels it already quickened as down and walks on to the next group, a slam press cuts them. `builder.skipMode('quicken')` sets the default for a set, `HoldAndWinBuilder.skipMode()` for a board, and `board.skip(options?)` takes the same object. Either mode is a skip: `skipStage` advances, `wasSkipped` is `true`, and `SpinResult.skipMode` says which. `slamStop()` stays a slam by name. In cascade mode a quicken slams and warns once with code `quicken-cascade`.
10
+
11
+ **Skip events carry the press.** `skip:requested` and `skip:completed` are `SkipInfo`, `{ reels, partial, mode, speed?, payload? }`: the same `SkipContext` every phase's `onSkip(ctx)` saw, plus the reels, so code hung off the event gets what the game attached to the press. `completed` fires in the same tick for a slam and as the last freed reel lands for a quicken, with the same `info`. Keys the press did not set are absent; an engine slam (abort, timeout, `slamStop()`) is a bare `mode: 'slam'`. `SpinResult.skipContext` is the last such press, beside `skipMode`. `feature:skip` on a board is `{ inFlight, mode, speed?, payload? }` with the press's options as given. A `'slam'` listener written for 2.x keeps working; one that meant "about to be placed" now checks `mode`.
12
+
13
+ **`onSkip(ctx)`.** `ReelPhase.skip(ctx?)`, `forceComplete(ctx?)` and the `onSkip(ctx: SkipContext)` hook receive `{ mode, speed?, payload? }`. Under `'slam'` the hook is the slam pose as before and the base completes the phase after it. Under `'quicken'` only the hook runs: cut a wait, never the landing, and call `_complete()` yourself when the natural end is reached (right away if the slam pose already is that end); ignore the mode and the phase runs its course, so the reel still lands. The built-in hooks default `ctx` to a slam, so a subclass that still calls `super.onSkip()` keeps its 2.x meaning; pass `ctx` through to take part in quickens. `payload` is whatever the game attached to the press. A reel quickened before it reached its stop has the stop phase primed and asked as soon as it is created: its `cut` steps are skipped from the first one on, and `onSkip(ctx)` follows `onEnter` as usual. This is the shape every phase hook will take; `onEnter` and `update` are unchanged for now.
14
+
15
+ **Steps.** The built-in phases run a named list of steps, and a game edits that list by re-registering the same class with options: `f.register('stop', StopPhase, { steps: (steps) => insertAfter(steps, 'land', step('flash', (ctx) => ...)) })`. `StopPhase` runs `delay, spinOut, land, bounce`; `StartPhase` runs `delay, launch, pull, accelerate, announce`; `AnticipationPhase` runs one `tease` step. A step returns a gsap tween or timeline (killed on a slam), a promise (told to stop through `ctx.signal`), a cancellable such as `ctx.phase.bounce()`, or nothing; `ctx` carries the reel, the profile, the config, gsap, the container and its travel-axis property, the phase, and `wait(ms)` / `until(predicate)`. `insertBefore`, `insertAfter`, `replaceStep`, `removeStep` and `runStep` are exported. A quicken skips the steps marked `cut` (the delays and the tease). `ReelPhase.runSteps()` / `tickSteps()` let a custom phase run its own list; `land()` and `bounce()` are public, and `bounce({ animation })` takes a different shape while the base keeps carrying lifted unmask symbols along.
16
+
17
+ **`quickenable`.** A quicken reaches `onSkip(ctx)` only on a phase that declares `readonly quickenable = true`. A phase without it is left alone by a quicken and runs its course, so a 2.8 phase whose slam pose kills its tweens keeps working. The built-ins declare it.
18
+
19
+ **More to listen to.** `skip:queued` on the set bus when `requestSkip()` comes before the result, with the press on it. `phase:step` on `reel.events` for every step of a phase on `runSteps()`: `start`, `end`, `skipped` (a `cut` step a quicken never started), `cut` (the `cut` step a quicken stopped in flight), `cancelled` (the step a slam stopped). `EventEmitter.onAny(fn)` / `offAny(fn)` on every bus, name first, after the event's own listeners; `enableDebug().trace()` now runs on it, covers every event, and returns the function that stops it. `onNotice(fn)` hears every engine notice whatever the console level, as `{ kind, code, message, detail }`.
20
+
21
+ **Open internals.** Every `private` field and method on `StartPhase`, `SpinPhase`, `AnticipationPhase`, `StopPhase`, `AdjustPhase` and the three cascade phases is `protected`, so a subclass can reach `_beginSpinOut`, `_landAndBounce`, `_stage`, `_launch`, `_runSegment` and the rest instead of rewriting the phase. `ReelPhase.speed` is a public getter beside `reel`.
22
+
23
+ **Also.** `skipStage` is decided before a slam lands anything, so a `spin:complete` listener sees `2` on the press that ended the round (it saw `0` before). `slamStop()` before `setResult()` throws like `skipSpin()` instead of warning `slam-before-result` and landing on random fill; a spin whose result will never come is aborted through its `spin({ signal })`.
24
+
25
+ **Removed.** `requestHurry()`, `board.hurry()`, `BoardGrid.hurrySpinning()`, `HurryOptions`, the `hurry:requested` and `feature:hurry` events, `ReelPhase.hurry()` / `onHurry()` and the `slam-before-result` notice. None of these shipped to npm.
26
+
27
+ ### Minor Changes
28
+
29
+ - [#251](https://github.com/schmooky/pixi-reels/pull/251) [`bc2b5cb`](https://github.com/schmooky/pixi-reels/commit/bc2b5cb45dc890be445fba0ade03aabad18e404e) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `PhaseCardSymbol` - a `CardSymbol` that is grey at rest and takes the colour of the phase its reel is running, so a spin can be read straight off the board: sky while the reel accelerates, blue at full speed, amber through a tease, violet while the stop spins the frame in, a green beat on landing, then grey again. Debug scaffolding with the same status as `CardSymbol`, not production art.
30
+
31
+ The card does not know its reel. `PhaseCardSymbol.watch(reelSet.reels)` paints every card on those reels from each reel's own `phase:enter`, `landed` and `symbol:created` events (a card swapped in mid-phase is painted on arrival, other symbol classes are left alone) and returns the release. Custom phases are painted by their key, `other` when `PHASE_CARD_COLORS` has no entry; `colors` overrides or extends the map per registration, `landedMs` sets the landed beat, and `setPhase()` is public for anything else that knows the phase.
32
+
33
+ - [#250](https://github.com/schmooky/pixi-reels/pull/250) [`34d3044`](https://github.com/schmooky/pixi-reels/commit/34d304425981a265c3e28c90d25d362e3eee3bb8) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: a phase-authoring contract, so a phase written on `ReelPhase` can land a reel without a cast. `pixi-reels` invites custom phases (`PhaseFactory.register`, `ReelPhase` exported as the base class) and then hid every method those phases need behind `@internal`; the library's own recipe called them under `@ts-nocheck`.
34
+
35
+ - `ReelPhase.land(cells?)` (protected) brings the reel to rest on the frame it shows and announces the landing: drive halted, strip snapped to the grid, `onReelSpinEnd()` then `onReelLanded()` on the symbols, unmask symbols lifted, `spin:reelLanding` raised. It is what `StopPhase` does between its spin-out and its bounce, and the only way a phase lands a reel.
36
+ - `ReelPhase.bounce(options?)` (protected) plays the landing overshoot and returns a `ReelBounce` (`done` promise, `cancel()`), carrying lifted unmask views along the way as `StopPhase` always had to by hand. Defaults come from the phase's profile; `BounceOptions` (`distance`, `duration`, `ease`) override them per call. `StopPhase` now lands and bounces through both helpers.
37
+ - `Reel.placeStrip()`, `Reel.beginMotion()`, `Reel.notifySpinStart()` and `Reel.forceSpeed()` are public, documented as the phase contract and present in the published typings. `haltDrive`, `snapToGrid`, `notifySpinEnd`, `notifyLanded` and `offsetLiftedViews` stay internal: `land()` and `bounce()` cover them.
38
+ - `ReelPhase<TConfig, TProfile extends SpeedProfile = SpeedProfile>`: a phase that carries its own timing on the speed profile (`interface InstantProfile extends SpeedProfile { slideMs: number }`) declares it, and `this._speed` is typed to see the extra fields. `PhaseFactory.register` / `registerFactory` infer the profile from the class, so the registration needs no cast either. The manager already handed every phase the registered profile instance by reference; this makes that first-class.
39
+
40
+ A consumer that writes no custom phase sees no change.
41
+
42
+ ### Patch Changes
43
+
44
+ - [#251](https://github.com/schmooky/pixi-reels/pull/251) [`bc2b5cb`](https://github.com/schmooky/pixi-reels/commit/bc2b5cb45dc890be445fba0ade03aabad18e404e) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Fix: `ReelSet.destroy()` no longer throws when a pooled `CardSymbol` is disposed after its reel. A released symbol's view stays a child of the reel container, so `Reel.destroy()` had already destroyed it by the time the symbol pool disposed the symbol, and `CardSymbol.stopAnimation()` reset the scale of a label that no longer had one. `ReelSymbol.destroy()` now skips `stopAnimation()` and `onDeactivate()` for a symbol whose view is already destroyed; `onDestroy()` still runs.
45
+
46
+ ## 2.8.0
47
+
48
+ ### Minor Changes
49
+
50
+ - [#241](https://github.com/schmooky/pixi-reels/pull/241) [`0781855`](https://github.com/schmooky/pixi-reels/commit/0781855fdaa79883e7927507b72f5acd95eb3a39) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `BoardGrid.lift(cell)` / `HoldAndWinBoard.lift(cell)` - draw one cell's lifted art in front of every other cell's until the returned release is called, for a symbol whose one-shot must not be overlapped by a neighbour's art.
51
+
52
+ Promotion is a second render layer rather than a bigger `zIndex`, so it is a separate channel from the at-rest order: `refreshCellZIndex()` keeps ordering the lifted cell underneath the lift - and the board keeps calling it on every `place` and every landing - while the release restores the cell's place exactly, recomputing nothing. Writing `zIndex` by hand cannot express this: any landing mid-animation drops the cell back into the pack.
53
+
54
+ Lifts are reference-counted per cell, releases are idempotent, and `liftedCells` reports what is up. `reset()` and `destroy()` drop every outstanding lift; `respin()` deliberately does not, so a presentation may span one. The lift covers the art the engine hoists out of a cell for a symbol registered `unmask: true`; a masked cell is clipped to itself and cannot overlap a neighbour, so lifting one is a documented no-op.
55
+
56
+ Add: `BoardGrid.dim({ except, amount, fade })` / `HoldAndWinBoard.dim(...)` - push every cell but the named ones into the background for a beat, the partner of `lift()`. One rectangle per dimmed cell, drawn above the cells' lifted art and below anything lifted, so a lifted cell stays out in front of the dim. It fades in and back out over `fade` ms (`DEFAULT_DIM_FADE_MS`, 180) off the board's own ticker, and an interrupted fade resumes from what is on screen over the proportional share of the duration, so nothing jumps; `fade: 0` cuts. Only one dim at a time is meaningful, so a second call throws instead of silently replacing the first.
57
+
58
+ Add: `BoardGrid.dimSymbols({ except, amount, fade })` / `HoldAndWinBoard.dimSymbols(...)` - the same shape as `dim()` on a different channel: it multiplies each symbol view's `tint` towards black and leaves the cell alone, so the board's chrome, backgrounds and gaps stay as bright as they were and only the art sinks. The tint carries down the whole view, a Spine skeleton included, without touching what a symbol class tints internally. `amount` is the strength (`DEFAULT_DIM_AMOUNT`, 0.5, which is the classic `tint: 0x808080`); `0` leaves the art alone and `1` takes it to black. A dimmed cell that swaps its symbol keeps its dim: the arrival is tinted and the departure is cleaned. The two dims run on separate fade slots, so a game can sink the cells and the art at once.
59
+
60
+ Fix: `ReelSymbol.activate()` and `deactivate()` now reset `view.tint` alongside alpha, scale, rotation and filters, and so does the same-id in-place swap in `Reel._replaceSymbol` (which never touches the pool). Tint was the one mutable visual the recycle did not clear, so a symbol released while tinted - by the new board dim, or by a game tinting art of its own - carried that colour into whatever cell the pool handed it to next.
61
+
62
+ Add: `ReelSet.promote(positions)` - raise symbols above the mask and above every other symbol, with no dim, no `playWin()` and nothing to await, which previously meant asking `spotlight.show()` for a whole presentation and switching every part of it off. Views attach to the new `ReelViewport.promotedLayer` instead of being reparented, so nothing moves and nothing has to be put back. A swap under a promoted symbol ends that symbol's promotion, so the layer never holds a view the pool has taken back.
63
+
64
+ Add: `HoldAndWinBoard.refreshCellZIndex()` - already public on `BoardGrid` but unreachable from the board a game holds, which left a resolver depending on state the library does not watch with no way to say "ask me again".
65
+
66
+ A consumer that calls none of these sees no behaviour change.
67
+
3
68
  ## 2.7.0
4
69
 
5
70
  ### Minor 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}_landing=null;get landing(){return this._landing}trackLanding(e){return this._landing=e,e}resetLanding(){this._landing=null}get isDestroyed(){return this._isDestroyed}activate(e){this._symbolId=e,this.view.visible=!0,this.view.alpha=1,this.view.scale.set(1,1),this.view.rotation=0,this.view.filters=null,this.view.tint=16777215,this.view.zIndex=0,this.onActivate(e)}deactivate(){this.stopAnimation(),this.onDeactivate(),this._symbolId=``,this._landing=null,this.view.visible=!1,this.view.alpha=1,this.view.scale.set(1,1),this.view.rotation=0,this.view.filters=null,this.view.tint=16777215,this.view.zIndex=0}reset(){this.deactivate()}destroy(){this._isDestroyed||=(this.view.destroyed||(this.stopAnimation(),this.onDeactivate()),this.onDestroy(),this.view.destroyed||this.view.destroy({children:!0}),!0)}onDestroy(){}async playLanding(){}async playDestroy(e){let t=this.view,n=t.pivot.x,r=t.pivot.y,i=t.x,a=t.y,o=t.getLocalBounds(),s=o.x+o.width/2,c=o.y+o.height/2;t.pivot.set(s,c),t.x=i+(s-n)*t.scale.x,t.y=a+(c-r)*t.scale.y;let l=e?.delay??0,u=e?.signal,d=()=>{t.alpha=0,t.scale.set(0,0)};if(u?.aborted){d(),t.pivot.set(n,r),t.x=i,t.y=a,t.scale.set(1,1),t.alpha=0;return}await new Promise(e=>{let n=this.gsap.timeline({onComplete:()=>{u&&u.removeEventListener(`abort`,r),e()},delay:l}).to(t.scale,{x:1.1,y:1.1,duration:.06,ease:`back.out(2.5)`}).to(t.scale,{x:0,y:0,duration:.14,ease:`power3.in`},`<+=0.04`).to(t,{alpha:0,duration:.14,ease:`power3.in`},`<`),r=()=>{n.kill(),d(),e()};u&&u.addEventListener(`abort`,r,{once:!0})}),t.pivot.set(n,r),t.x=i,t.y=a,t.scale.set(1,1)}async playOut(e){await this._tweenPresence(`out`,e)}async playIn(e){await this._tweenPresence(`in`,e)}async _tweenPresence(e,t){let n=this.view,r=n.pivot.x,i=n.pivot.y,a=n.x,o=n.y,s=n.getLocalBounds(),c=s.x+s.width/2,l=s.y+s.height/2;n.pivot.set(c,l),n.x=a+(c-r)*n.scale.x,n.y=o+(l-i)*n.scale.y;let u=()=>{n.pivot.set(r,i),n.x=a,n.y=o},d=()=>{e===`out`?(n.alpha=0,n.scale.set(1,1)):(n.visible=!0,n.alpha=1,n.scale.set(1,1))},f=t?.delay??0,p=t?.signal;if(e===`in`&&(n.visible=!0,n.alpha=0,n.scale.set(.72,.72)),p?.aborted){d(),u();return}await new Promise(t=>{let r=()=>{i.kill(),d(),t()},i=this.gsap.timeline({delay:f,onComplete:()=>{p&&p.removeEventListener(`abort`,r),t()}});e===`out`?i.to(n.scale,{x:.72,y:.72,duration:.18,ease:`power2.in`}).to(n,{alpha:0,duration:.18,ease:`power2.in`},`<`):i.to(n.scale,{x:1,y:1,duration:.2,ease:`back.out(1.7)`}).to(n,{alpha:1,duration:.16,ease:`power2.out`},`<`),p&&p.addEventListener(`abort`,r,{once:!0})}),n.scale.set(1,1),u()}get cellInset(){return null}applyCellQuad(e){let t=this.view;if(e===null){t.scale.set(1,1),t.pivot.set(0,0);return}let n=(Math.hypot(e.x1-e.x0,e.y1-e.y0)+Math.hypot(e.x2-e.x3,e.y2-e.y3))/2,r=Math.hypot((e.x3+e.x2)/2-(e.x0+e.x1)/2,(e.y3+e.y2)/2-(e.y0+e.y1)/2),i=e.width>0&&e.height>0?Math.min(n/e.width,r/e.height):1,a=(e.x0+e.x1+e.x2+e.x3)/4,o=(e.y0+e.y1+e.y2+e.y3)/4;t.scale.set(i,i),t.pivot.set(e.x+e.width/2-a/i,e.y+e.height/2-o/i)}onReelSpinStart(e){}onReelSpinEnd(){}onReelAnticipationStart(){}onReelLanded(e){}};Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return n}});
2
+ //# sourceMappingURL=ReelSymbol-B5QBhkmw.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-B5QBhkmw.cjs","names":[],"sources":["../src/utils/gsap.ts","../src/symbols/ReelSymbol.ts"],"sourcesContent":["import { gsap as resolvedGsap } from 'gsap';\n\n/** The gsap namespace type, as the engine passes it around. */\nexport type Gsap = typeof resolvedGsap;\n\n/**\n * The gsap instance resolved at lib-load time. Used by any `ReelSet` whose\n * builder did not call `.gsap(...)`, and by any symbol not yet bound to a\n * set.\n *\n * **Why the engine passes gsap around at all:** under tools that resolve\n * modules through symlinked workspaces (vite + a locally-linked pixi-reels,\n * pnpm dev setups, esbuild plugin chains), the gsap import inside the lib's\n * compiled `dist/index.js` and the gsap import in the consumer's source can\n * resolve to *different module instances*. each with its own root timeline.\n * The consumer drives one, the lib's tweens live on the other, and reels\n * stall at progress 0. `ReelSetBuilder.gsap(myGsap)` hands the engine the\n * consumer's instance so both live on the same timeline.\n *\n * Held PER REEL SET, not process-wide: two sets on one stage can be driven\n * by different gsap instances, which is what a composed stage (a banner reel\n * above a main grid, a bonus board beside it) needs.\n */\nexport const DEFAULT_GSAP: Gsap = resolvedGsap;\n","import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelCellInset, ReelCellQuad, ReelLandingContext } from '../config/types.js';\nimport { DEFAULT_GSAP, type Gsap } from '../utils/gsap.js';\n\n/**\n * One visible cell on a reel. the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)`. the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()`. I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()`. the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()`. spotlight is over, return to idle.\n * - `resize(w, h)`. the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n private _gsap: Gsap = DEFAULT_GSAP;\n private _mainAxis: 'x' | 'y' = 'y';\n\n constructor() {\n this.view = new Container();\n }\n\n /**\n * The gsap instance this symbol should animate on. Use it instead of\n * importing `gsap` in a subclass: under a symlinked-workspace module\n * resolution your import and the engine's can be different instances, and\n * only this one is on the timeline the reel set actually drives.\n *\n * Bound to the owning set by `SymbolFactory`; falls back to the instance\n * resolved at lib-load time for a symbol built outside a set.\n */\n protected get gsap(): Gsap {\n return this._gsap;\n }\n\n /**\n * @internal. Called by `SymbolFactory` when the symbol is created, so a\n * pooled symbol animates on its own set's gsap rather than whichever set\n * happened to build last.\n */\n bindGsap(instance: Gsap): void {\n this._gsap = instance;\n }\n\n /**\n * The screen axis the owning set's strips travel along: `'y'` for a\n * vertical set, `'x'` for a horizontal one.\n *\n * Symbols are otherwise orientation-agnostic - `resize(width, height)` is\n * screen-space and always will be. This exists for the few effects that\n * genuinely follow travel, motion blur being the one in the box.\n */\n protected get mainAxis(): 'x' | 'y' {\n return this._mainAxis;\n }\n\n /** @internal. Bound by `SymbolFactory` from the set's orientation. */\n bindMainAxis(prop: 'x' | 'y'): void {\n this._mainAxis = prop;\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n private _landing: Promise<void> | null = null;\n\n /**\n * The landing beat this symbol started on its latest `onReelLanded()`, or\n * `null` if it started none. Stays set (resolved) until the reel moves\n * again or the symbol is pooled, so a presenter that lands cells one at a\n * time - `HoldAndWinBoard` on a lock, a win spotlight - can sequence after\n * the landing (`await symbol.landing`) instead of stomping it, and can\n * tell a symbol that already landed from one that still needs\n * `playLanding()`.\n *\n * Subclasses report a landing through {@link trackLanding}.\n */\n get landing(): Promise<void> | null {\n return this._landing;\n }\n\n /**\n * Subclass helper: record `run` as this symbol's landing beat (see\n * {@link landing}) and return it. `SpineReelSymbol` wraps its landing\n * one-shot in it; a sprite symbol wraps its settle tween.\n */\n protected trackLanding(run: Promise<void>): Promise<void> {\n this._landing = run;\n return run;\n }\n\n /** @internal Called by the reel when it leaves rest; the landing is over. */\n resetLanding(): void {\n this._landing = null;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity. Called when the symbol enters\n * the visible reel or is recycled from the pool. Resets container\n * transform / filter state for parity with deactivate().\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // Tint is a mutable visual like the rest: a symbol arriving from the pool\n // must not wear a colour some previous cell put on it. Cleared on both\n // sides of the recycle so neither half has to trust the other.\n this.view.tint = 0xffffff;\n this.view.zIndex = 0;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool. Stops\n * animations, hides the view, and resets container transform / filter\n * state so subclass decorations don't leak across recycles.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this._landing = null;\n this.view.visible = false;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // See activate(). A symbol dimmed by the board (or tinted by a game) is\n // released to the pool the moment its cell swaps, and the pool hands the\n // same instance to a different cell - which would inherit the colour.\n this.view.tint = 0xffffff;\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 // A pooled symbol's view stays a child of the reel container it was\n // released from, so `Reel.destroy()` has already destroyed it (children\n // included) by the time the pool disposes the symbol. There is nothing\n // left to stop or clean visually, and a `stopAnimation()` that reaches\n // into a destroyed child (`CardSymbol` resets its label's scale) throws.\n if (!this.view.destroyed) {\n this.stopAnimation();\n this.onDeactivate();\n }\n this.onDestroy();\n if (!this.view.destroyed) this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {}\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /**\n * Play the landing beat for this symbol - the settle a symbol does as its\n * reel stops, before (or instead of) any celebration. Resolves when done.\n *\n * The reel engine never calls this itself: `onReelLanded()` is the\n * engine-driven hook, fired on every landed symbol. This is the explicit\n * counterpart for a caller that lands cells one at a time - `HoldAndWinBoard`\n * calls it on each lock when built with `lockAnimation('landing')`.\n *\n * Default: resolves at once. `SpineReelSymbol` overrides it with the\n * skeleton's `landing` track; a sprite symbol can override it with a tween.\n */\n async playLanding(): Promise<void> {}\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Play the cascade-destruction animation for this symbol. Called by\n * consumers (typically via `reelSet.destroySymbols(...)`) to disintegrate\n * a winning cell before the next cascade refill drops fresh symbols in.\n *\n * Override in subclasses for art-appropriate destruction, e.g. a Spine\n * symbol can play its `disintegration` track here, or a sprite symbol can\n * swap to a shatter atlas. The promise must resolve when the symbol is no\n * longer visible.\n *\n * Default: a snappy \"poof\" centered on the symbol's bounds regardless of\n * the view's anchor. Tiny anticipation pop (~60 ms) then a fast implode to\n * `scale: 0` + `alpha: 0` (~140 ms), ~200 ms total, no rotation. Reads\n * cleanly under win-cluster pacing without competing with the win\n * presenter. The view is left at `alpha: 0` (destroyed); position / pivot\n * are restored so pool reuse via `_replaceSymbol`'s same-id fast path\n * doesn't inherit a stale pivot offset.\n *\n * `opts.delay`. seconds to wait before the animation starts. Use to\n * stagger a cluster of winners (e.g. `i * 0.015`).\n * `opts.signal`. abort signal. If aborted (now or mid-animation), the\n * tween is killed and the view is snapped to its destroyed pose\n * (`alpha: 0`, transform restored). The promise resolves normally. abort\n * means \"skip to the end,\" not \"fail\". Subclasses that override this\n * method MUST honor the signal or document why they can't (e.g. a Spine\n * `disintegration` track is uninterruptible).\n */\n async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n const view = this.view;\n // Capture original transform so pool reuse sees a clean state.\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n // Pivot to bounds-center so the scale collapses around the visual\n // centre instead of the view's (0,0) corner. and compensate position\n // so the symbol doesn't visibly jump when the pivot moves.\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n // Moving the pivot moves the view by `delta * scale`, not by `delta`: a\n // container renders a local point at `position + (point - pivot) * scale`.\n // Scale is 1 on a plain reel, which is why dropping it went unnoticed, but\n // a curved reel scales every cell and the symbol would jump on destroy.\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n const snapDestroyed = (): void => {\n view.alpha = 0;\n view.scale.set(0, 0);\n };\n\n // Pre-abort: skip the tween entirely and snap to the destroyed pose.\n if (signal?.aborted) {\n snapDestroyed();\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n view.alpha = 0;\n return;\n }\n\n await new Promise<void>((resolve) => {\n const tl = this.gsap\n .timeline({ onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, delay })\n // Brief anticipation pop. small upscale, ~60 ms, with overshoot\n // so the implode reads as a release. No rotation.\n .to(view.scale, { x: 1.1, y: 1.1, duration: 0.06, ease: 'back.out(2.5)' })\n // Snap implode. scale -> 0 + alpha -> 0 together, snappy ease-in\n // so the symbol collapses into the cell centre and is gone.\n .to(view.scale, { x: 0, y: 0, duration: 0.14, ease: 'power3.in' }, '<+=0.04')\n .to(view, { alpha: 0, duration: 0.14, ease: 'power3.in' }, '<');\n\n const onAbort = (): void => {\n tl.kill();\n snapDestroyed();\n resolve();\n };\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Restore transform. alpha stays 0 (the symbol IS destroyed). Scale\n // restored to 1 so pool reuse via `_replaceSymbol`'s same-id fast path\n // doesn't inherit a stale 0× scale; _replaceSymbol also resets scale\n // explicitly but a defensive restore here makes the destroyed cell\n // observably \"ready to be re-skinned\" between calls.\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n }\n\n /**\n * Animate this symbol AWAY, without destroying it.\n *\n * The counterpart to {@link playIn}, and the seam a mystery reveal needs:\n * the cells on screen dissolve, their identities are swapped underneath, and\n * the new ones arrive. `playDestroy` is the cascade's version of the same\n * beat and is deliberately separate - it is tuned as a \"this cell was a\n * winner and is being consumed\" poof, and a reveal is not that.\n *\n * Leaves the view hidden (`alpha: 0`) with its transform restored, so the\n * caller may swap the identity and call `playIn` next.\n *\n * Override for art-appropriate exits - a Spine symbol plays its own `out`\n * track here. Honour `opts.signal`: abort means \"snap to the end\", not\n * \"fail\", so the promise still resolves.\n *\n * Default: a ~180 ms shrink-and-fade with no overshoot, centred on the\n * symbol's bounds rather than the view origin.\n */\n async playOut(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('out', opts);\n }\n\n /**\n * Animate this symbol IN, from nothing to its resting pose.\n *\n * Owns its own start pose. A symbol that has just been re-activated is fully\n * visible (`activate()` resets alpha and scale), so an entrance that assumed\n * it started hidden would pop before it animated. This sets the hidden pose\n * first, then plays.\n *\n * Override for art-appropriate entrances - a Spine symbol plays its own `in`\n * track here. Honour `opts.signal`: abort snaps to the RESTING pose (alpha 1,\n * scale 1), because arriving is what the caller asked for.\n *\n * Default: a ~200 ms fade and scale-up with a small overshoot.\n */\n async playIn(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('in', opts);\n }\n\n /**\n * Shared body of {@link playIn} / {@link playOut}.\n *\n * One function because the two are the same tween read in opposite\n * directions, and because the fiddly part - pivoting to the visual centre so\n * the scale does not collapse toward the view's (0, 0) corner, and\n * compensating the position for the pivot move - is identical and easy to get\n * subtly wrong twice. Same correction as `playDestroy`: a container renders a\n * local point at `position + (point - pivot) * scale`, so a curved reel (which\n * scales every cell) would make the symbol jump without the `* scale` term.\n */\n private async _tweenPresence(\n direction: 'in' | 'out',\n opts?: { delay?: number; signal?: AbortSignal },\n ): Promise<void> {\n const view = this.view;\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const restorePivot = (): void => {\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n };\n const snapEnd = (): void => {\n if (direction === 'out') {\n view.alpha = 0;\n view.scale.set(1, 1);\n } else {\n view.visible = true;\n view.alpha = 1;\n view.scale.set(1, 1);\n }\n };\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n if (direction === 'in') {\n // Own the start pose: `activate()` leaves a freshly swapped symbol fully\n // visible, so without this the new art is on screen for a frame before\n // the entrance begins.\n view.visible = true;\n view.alpha = 0;\n view.scale.set(0.72, 0.72);\n }\n\n if (signal?.aborted) {\n snapEnd();\n restorePivot();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => {\n tl.kill();\n snapEnd();\n resolve();\n };\n const tl = this.gsap.timeline({\n delay,\n onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n },\n });\n if (direction === 'out') {\n tl.to(view.scale, { x: 0.72, y: 0.72, duration: 0.18, ease: 'power2.in' }).to(\n view,\n { alpha: 0, duration: 0.18, ease: 'power2.in' },\n '<',\n );\n } else {\n tl.to(view.scale, { x: 1, y: 1, duration: 0.2, ease: 'back.out(1.7)' }).to(\n view,\n { alpha: 1, duration: 0.16, ease: 'power2.out' },\n '<',\n );\n }\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Scale back to 1 either way: a hidden symbol still has to be a legal\n // resting pose, or pool reuse inherits the shrunken scale.\n view.scale.set(1, 1);\n restorePivot();\n }\n\n /**\n * The owning reel is curved: render into this projected quad instead of the\n * flat cell rectangle. `null` means the reel is flat again.\n *\n * The quad's corners are SCREEN-space and local to this view's own origin,\n * clockwise from top-left, and `width` / `height` give the flat cell box the\n * quad replaces. Called on every frame of motion and on every placement, so\n * it must be cheap and idempotent.\n *\n * **The default is an approximation.** A `Container` transform is affine, so\n * it cannot express a trapezoid; the base class fits the quad as closely as\n * an affine transform can - a UNIFORM scale about the quad's centre. Uniform\n * on purpose: symbol art is usually smaller than its cell and has a shape a\n * player recognises, and squashing one axis turns a `7` into a squashed `7`\n * rather than a `7` seen at an angle.\n *\n * Override this to render the real perspective. {@link SpriteSymbol} and\n * {@link AnimatedSpriteSymbol} do, by drawing their texture through a\n * `PerspectiveMesh`, which costs no extra render pass because the content is\n * already a texture. A symbol whose content is an arbitrary subtree (Spine, a\n * composite of sprites and text) cannot do that without rendering itself to a\n * texture every frame, so it keeps the affine fit.\n *\n * Whatever you do here, do NOT move `view.position`: the reel reads that\n * coordinate back to work out which slot this symbol is in.\n */\n /**\n * The part of its cell this symbol's art actually covers, or `null` (the\n * default) for \"all of it\".\n *\n * Slot art is usually smaller than its cell - a trimmed atlas frame is a\n * shape floating in a much bigger transparent box - and the reel needs to\n * know that to project the rectangle the art is really in. Overriding this\n * is what stops a small symbol being inflated to the cell's edges and given\n * the cell's keystone instead of its own, milder one.\n *\n * Read once per projection, so it may change with the symbol's identity.\n */\n get cellInset(): ReelCellInset | null {\n return null;\n }\n\n applyCellQuad(quad: ReelCellQuad | null): void {\n const view = this.view;\n if (quad === null) {\n view.scale.set(1, 1);\n view.pivot.set(0, 0);\n return;\n }\n // Measure the trapezoid: the mean of its two parallel edges, and the\n // distance between their midpoints.\n const nearWidth = Math.hypot(quad.x1 - quad.x0, quad.y1 - quad.y0);\n const farWidth = Math.hypot(quad.x2 - quad.x3, quad.y2 - quad.y3);\n const across = (nearWidth + farWidth) / 2;\n const along = Math.hypot(\n (quad.x3 + quad.x2) / 2 - (quad.x0 + quad.x1) / 2,\n (quad.y3 + quad.y2) / 2 - (quad.y0 + quad.y1) / 2,\n );\n // CONTAIN, not cover. A quad in the middle of the window is TALLER than\n // the flat cell (that is the drum magnifying what faces you), so a scale\n // picked to fill it would also make the symbol wider than its column and\n // overlap its neighbours - very visible on art that fills its cell\n // edge-to-edge. Taking the smaller ratio keeps every symbol inside its own\n // projected footprint at the cost of a little slack on one axis.\n const scale =\n quad.width > 0 && quad.height > 0\n ? Math.min(across / quad.width, along / quad.height)\n : 1;\n\n const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n view.scale.set(scale, scale);\n // A container renders a local point at `position + (point - pivot) * scale`\n // and `position` must not move, so putting the flat box's centre on the\n // quad's centre has to be paid for entirely out of the pivot.\n view.pivot.set(\n quad.x + quad.width / 2 - cx / scale,\n quad.y + quad.height / 2 - cy / scale,\n );\n }\n\n /**\n * Lifecycle hook: the owning reel is spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur,\n * StaticSpinSymbol) to swap to a spin presentation automatically.\n *\n * Fired on every strip symbol (visible AND buffer cells) when the reel\n * enters the spin phase, and again with `joinedMidSpin: true` on each\n * symbol freshly installed while the reel is already spinning (pool\n * recycling wipes symbol state, so a wrapped-in symbol can't know the\n * reel is moving without this). Implementations MUST be idempotent.\n * the same instance can be notified more than once per spin.\n *\n * @param joinedMidSpin true when this symbol was installed into a reel\n * already at speed (skip start-of-spin transitions like blur ramps).\n */\n onReelSpinStart(joinedMidSpin?: boolean): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel entered its anticipation (tease) phase.\n * it is still spinning, but slowed enough that the strip is readable.\n * Spin presentations that obscure symbols (blur textures, smear\n * animations) should relax so the player can follow the tease. Also\n * fired on symbols installed while the reel is anticipating.\n * Implementations MUST be idempotent. Default: no-op.\n */\n onReelAnticipationStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n *\n * `ctx` says which reel and cell this symbol landed in, so an override can\n * play a different beat - or none - on a particular reel. The engine always\n * supplies it; it is optional only so an override written as\n * `onReelLanded()` keeps compiling. Fired before the reel's `landing` event\n * and the set's `spin:reelLanding`, by contract.\n */\n onReelLanded(ctx?: ReelLandingContext): void {}\n}\n"],"mappings":"yBAuBA,IAAa,gBAAqB,CAAA,CAAA,KCYZ,EAAtB,KAAuD,CAErD,KAEA,UAA4B,GAC5B,aAAuB,GAEvB,MAAsB,EACtB,UAA+B,IAE/B,aAAc,CACZ,KAAK,KAAO,IAAI,EAAA,SAClB,CAWA,IAAc,MAAa,CACzB,OAAO,KAAK,KACd,CAOA,SAAS,EAAsB,CAC7B,KAAK,MAAQ,CACf,CAUA,IAAc,UAAsB,CAClC,OAAO,KAAK,SACd,CAGA,aAAa,EAAuB,CAClC,KAAK,UAAY,CACnB,CAEA,IAAI,UAAmB,CACrB,OAAO,KAAK,SACd,CAEA,SAAyC,KAazC,IAAI,SAAgC,CAClC,OAAO,KAAK,QACd,CAOA,aAAuB,EAAmC,CAExD,MADA,MAAK,SAAW,EACT,CACT,CAGA,cAAqB,CACnB,KAAK,SAAW,IAClB,CAEA,IAAI,aAAuB,CACzB,OAAO,KAAK,YACd,CAOA,SAAS,EAAwB,CAC/B,KAAK,UAAY,EACjB,KAAK,KAAK,QAAU,GACpB,KAAK,KAAK,MAAQ,EAClB,KAAK,KAAK,MAAM,IAAI,EAAG,CAAC,EACxB,KAAK,KAAK,SAAW,EACrB,KAAK,KAAK,QAAU,KAIpB,KAAK,KAAK,KAAO,SACjB,KAAK,KAAK,OAAS,EACnB,KAAK,WAAW,CAAQ,CAC1B,CAOA,YAAmB,CACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,UAAY,GACjB,KAAK,SAAW,KAChB,KAAK,KAAK,QAAU,GACpB,KAAK,KAAK,MAAQ,EAClB,KAAK,KAAK,MAAM,IAAI,EAAG,CAAC,EACxB,KAAK,KAAK,SAAW,EACrB,KAAK,KAAK,QAAU,KAIpB,KAAK,KAAK,KAAO,SACjB,KAAK,KAAK,OAAS,CACrB,CAGA,OAAc,CACZ,KAAK,WAAW,CAClB,CAEA,SAAgB,CACV,AAYJ,KAAK,gBANA,KAAK,KAAK,YACb,KAAK,cAAc,EACnB,KAAK,aAAa,GAEpB,KAAK,UAAU,EACV,KAAK,KAAK,WAAW,KAAK,KAAK,QAAQ,CAAE,SAAU,EAAK,CAAC,EAC1C,GACtB,CASA,WAA4B,CAAC,CAiB7B,MAAM,aAA6B,CAAC,CAmCpC,MAAM,YAAY,EAAgE,CAChF,IAAM,EAAO,KAAK,KAEZ,EAAiB,EAAK,MAAM,EAC5B,EAAiB,EAAK,MAAM,EAC5B,EAAY,EAAK,EACjB,EAAY,EAAK,EAKjB,EAAS,EAAK,eAAe,EAC7B,EAAK,EAAO,EAAI,EAAO,MAAQ,EAC/B,EAAK,EAAO,EAAI,EAAO,OAAS,EAKtC,EAAK,MAAM,IAAI,EAAI,CAAE,EACrB,EAAK,EAAI,GAAa,EAAK,GAAkB,EAAK,MAAM,EACxD,EAAK,EAAI,GAAa,EAAK,GAAkB,EAAK,MAAM,EAExD,IAAM,EAAQ,GAAM,OAAS,EACvB,EAAS,GAAM,OAEf,MAA4B,CAChC,EAAK,MAAQ,EACb,EAAK,MAAM,IAAI,EAAG,CAAC,CACrB,EAGA,GAAI,GAAQ,QAAS,CACnB,EAAc,EACd,EAAK,MAAM,IAAI,EAAgB,CAAc,EAC7C,EAAK,EAAI,EACT,EAAK,EAAI,EACT,EAAK,MAAM,IAAI,EAAG,CAAC,EACnB,EAAK,MAAQ,EACb,MACF,CAEA,MAAM,IAAI,QAAe,GAAY,CACnC,IAAM,EAAK,KAAK,KACb,SAAS,CAAE,eAAkB,CACxB,GAAQ,EAAO,oBAAoB,QAAS,CAAO,EACvD,EAAQ,CACV,EAAG,OAAM,CAAC,CAAC,CAGV,GAAG,EAAK,MAAO,CAAE,EAAG,IAAK,EAAG,IAAK,SAAU,IAAM,KAAM,eAAgB,CAAC,CAAC,CAGzE,GAAG,EAAK,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,SAAU,IAAM,KAAM,WAAY,EAAG,SAAS,CAAC,CAC5E,GAAG,EAAM,CAAE,MAAO,EAAG,SAAU,IAAM,KAAM,WAAY,EAAG,GAAG,EAE1D,MAAsB,CAC1B,EAAG,KAAK,EACR,EAAc,EACd,EAAQ,CACV,EACI,GAAQ,EAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,CACtE,CAAC,EAOD,EAAK,MAAM,IAAI,EAAgB,CAAc,EAC7C,EAAK,EAAI,EACT,EAAK,EAAI,EACT,EAAK,MAAM,IAAI,EAAG,CAAC,CACrB,CAqBA,MAAM,QAAQ,EAAgE,CAC5E,MAAM,KAAK,eAAe,MAAO,CAAI,CACvC,CAgBA,MAAM,OAAO,EAAgE,CAC3E,MAAM,KAAK,eAAe,KAAM,CAAI,CACtC,CAaA,MAAc,eACZ,EACA,EACe,CACf,IAAM,EAAO,KAAK,KACZ,EAAiB,EAAK,MAAM,EAC5B,EAAiB,EAAK,MAAM,EAC5B,EAAY,EAAK,EACjB,EAAY,EAAK,EAEjB,EAAS,EAAK,eAAe,EAC7B,EAAK,EAAO,EAAI,EAAO,MAAQ,EAC/B,EAAK,EAAO,EAAI,EAAO,OAAS,EACtC,EAAK,MAAM,IAAI,EAAI,CAAE,EACrB,EAAK,EAAI,GAAa,EAAK,GAAkB,EAAK,MAAM,EACxD,EAAK,EAAI,GAAa,EAAK,GAAkB,EAAK,MAAM,EAExD,IAAM,MAA2B,CAC/B,EAAK,MAAM,IAAI,EAAgB,CAAc,EAC7C,EAAK,EAAI,EACT,EAAK,EAAI,CACX,EACM,MAAsB,CACtB,IAAc,OAChB,EAAK,MAAQ,EACb,EAAK,MAAM,IAAI,EAAG,CAAC,IAEnB,EAAK,QAAU,GACf,EAAK,MAAQ,EACb,EAAK,MAAM,IAAI,EAAG,CAAC,EAEvB,EAEM,EAAQ,GAAM,OAAS,EACvB,EAAS,GAAM,OAWrB,GATI,IAAc,OAIhB,EAAK,QAAU,GACf,EAAK,MAAQ,EACb,EAAK,MAAM,IAAI,IAAM,GAAI,GAGvB,GAAQ,QAAS,CACnB,EAAQ,EACR,EAAa,EACb,MACF,CAEA,MAAM,IAAI,QAAe,GAAY,CACnC,IAAM,MAAsB,CAC1B,EAAG,KAAK,EACR,EAAQ,EACR,EAAQ,CACV,EACM,EAAK,KAAK,KAAK,SAAS,CAC5B,QACA,eAAkB,CACZ,GAAQ,EAAO,oBAAoB,QAAS,CAAO,EACvD,EAAQ,CACV,CACF,CAAC,EACG,IAAc,MAChB,EAAG,GAAG,EAAK,MAAO,CAAE,EAAG,IAAM,EAAG,IAAM,SAAU,IAAM,KAAM,WAAY,CAAC,CAAC,CAAC,GACzE,EACA,CAAE,MAAO,EAAG,SAAU,IAAM,KAAM,WAAY,EAC9C,GACF,EAEA,EAAG,GAAG,EAAK,MAAO,CAAE,EAAG,EAAG,EAAG,EAAG,SAAU,GAAK,KAAM,eAAgB,CAAC,CAAC,CAAC,GACtE,EACA,CAAE,MAAO,EAAG,SAAU,IAAM,KAAM,YAAa,EAC/C,GACF,EAEE,GAAQ,EAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,CACtE,CAAC,EAID,EAAK,MAAM,IAAI,EAAG,CAAC,EACnB,EAAa,CACf,CAwCA,IAAI,WAAkC,CACpC,OAAO,IACT,CAEA,cAAc,EAAiC,CAC7C,IAAM,EAAO,KAAK,KAClB,GAAI,IAAS,KAAM,CACjB,EAAK,MAAM,IAAI,EAAG,CAAC,EACnB,EAAK,MAAM,IAAI,EAAG,CAAC,EACnB,MACF,CAKA,IAAM,GAFY,KAAK,MAAM,EAAK,GAAK,EAAK,GAAI,EAAK,GAAK,EAAK,EAE/C,EADC,KAAK,MAAM,EAAK,GAAK,EAAK,GAAI,EAAK,GAAK,EAAK,EAClC,GAAY,EAClC,EAAQ,KAAK,OAChB,EAAK,GAAK,EAAK,IAAM,GAAK,EAAK,GAAK,EAAK,IAAM,GAC/C,EAAK,GAAK,EAAK,IAAM,GAAK,EAAK,GAAK,EAAK,IAAM,CAClD,EAOM,EACJ,EAAK,MAAQ,GAAK,EAAK,OAAS,EAC5B,KAAK,IAAI,EAAS,EAAK,MAAO,EAAQ,EAAK,MAAM,EACjD,EAEA,GAAM,EAAK,GAAK,EAAK,GAAK,EAAK,GAAK,EAAK,IAAM,EAC/C,GAAM,EAAK,GAAK,EAAK,GAAK,EAAK,GAAK,EAAK,IAAM,EACrD,EAAK,MAAM,IAAI,EAAO,CAAK,EAI3B,EAAK,MAAM,IACT,EAAK,EAAI,EAAK,MAAQ,EAAI,EAAK,EAC/B,EAAK,EAAI,EAAK,OAAS,EAAI,EAAK,CAClC,CACF,CAiBA,gBAAgB,EAA+B,CAAC,CAMhD,eAAsB,CAAC,CAUvB,yBAAgC,CAAC,CAajC,aAAa,EAAgC,CAAC,CAChD"}
@@ -39,16 +39,16 @@ var n = t, r = class {
39
39
  return this._isDestroyed;
40
40
  }
41
41
  activate(e) {
42
- this._symbolId = e, this.view.visible = !0, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0, this.onActivate(e);
42
+ this._symbolId = e, this.view.visible = !0, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.tint = 16777215, this.view.zIndex = 0, this.onActivate(e);
43
43
  }
44
44
  deactivate() {
45
- this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this._landing = null, this.view.visible = !1, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0;
45
+ this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this._landing = null, this.view.visible = !1, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.tint = 16777215, this.view.zIndex = 0;
46
46
  }
47
47
  reset() {
48
48
  this.deactivate();
49
49
  }
50
50
  destroy() {
51
- this._isDestroyed ||= (this.stopAnimation(), this.onDeactivate(), this.onDestroy(), this.view.destroyed || this.view.destroy({ children: !0 }), !0);
51
+ this._isDestroyed ||= (this.view.destroyed || (this.stopAnimation(), this.onDeactivate()), this.onDestroy(), this.view.destroyed || this.view.destroy({ children: !0 }), !0);
52
52
  }
53
53
  onDestroy() {}
54
54
  async playLanding() {}
@@ -156,4 +156,4 @@ var n = t, r = class {
156
156
  //#endregion
157
157
  export { n, r as t };
158
158
 
159
- //# sourceMappingURL=ReelSymbol-BvdqdXMY.js.map
159
+ //# sourceMappingURL=ReelSymbol-C7Pd7BeO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-C7Pd7BeO.js","names":[],"sources":["../src/utils/gsap.ts","../src/symbols/ReelSymbol.ts"],"sourcesContent":["import { gsap as resolvedGsap } from 'gsap';\n\n/** The gsap namespace type, as the engine passes it around. */\nexport type Gsap = typeof resolvedGsap;\n\n/**\n * The gsap instance resolved at lib-load time. Used by any `ReelSet` whose\n * builder did not call `.gsap(...)`, and by any symbol not yet bound to a\n * set.\n *\n * **Why the engine passes gsap around at all:** under tools that resolve\n * modules through symlinked workspaces (vite + a locally-linked pixi-reels,\n * pnpm dev setups, esbuild plugin chains), the gsap import inside the lib's\n * compiled `dist/index.js` and the gsap import in the consumer's source can\n * resolve to *different module instances*. each with its own root timeline.\n * The consumer drives one, the lib's tweens live on the other, and reels\n * stall at progress 0. `ReelSetBuilder.gsap(myGsap)` hands the engine the\n * consumer's instance so both live on the same timeline.\n *\n * Held PER REEL SET, not process-wide: two sets on one stage can be driven\n * by different gsap instances, which is what a composed stage (a banner reel\n * above a main grid, a bonus board beside it) needs.\n */\nexport const DEFAULT_GSAP: Gsap = resolvedGsap;\n","import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelCellInset, ReelCellQuad, ReelLandingContext } from '../config/types.js';\nimport { DEFAULT_GSAP, type Gsap } from '../utils/gsap.js';\n\n/**\n * One visible cell on a reel. the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)`. the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()`. I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()`. the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()`. spotlight is over, return to idle.\n * - `resize(w, h)`. the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n private _gsap: Gsap = DEFAULT_GSAP;\n private _mainAxis: 'x' | 'y' = 'y';\n\n constructor() {\n this.view = new Container();\n }\n\n /**\n * The gsap instance this symbol should animate on. Use it instead of\n * importing `gsap` in a subclass: under a symlinked-workspace module\n * resolution your import and the engine's can be different instances, and\n * only this one is on the timeline the reel set actually drives.\n *\n * Bound to the owning set by `SymbolFactory`; falls back to the instance\n * resolved at lib-load time for a symbol built outside a set.\n */\n protected get gsap(): Gsap {\n return this._gsap;\n }\n\n /**\n * @internal. Called by `SymbolFactory` when the symbol is created, so a\n * pooled symbol animates on its own set's gsap rather than whichever set\n * happened to build last.\n */\n bindGsap(instance: Gsap): void {\n this._gsap = instance;\n }\n\n /**\n * The screen axis the owning set's strips travel along: `'y'` for a\n * vertical set, `'x'` for a horizontal one.\n *\n * Symbols are otherwise orientation-agnostic - `resize(width, height)` is\n * screen-space and always will be. This exists for the few effects that\n * genuinely follow travel, motion blur being the one in the box.\n */\n protected get mainAxis(): 'x' | 'y' {\n return this._mainAxis;\n }\n\n /** @internal. Bound by `SymbolFactory` from the set's orientation. */\n bindMainAxis(prop: 'x' | 'y'): void {\n this._mainAxis = prop;\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n private _landing: Promise<void> | null = null;\n\n /**\n * The landing beat this symbol started on its latest `onReelLanded()`, or\n * `null` if it started none. Stays set (resolved) until the reel moves\n * again or the symbol is pooled, so a presenter that lands cells one at a\n * time - `HoldAndWinBoard` on a lock, a win spotlight - can sequence after\n * the landing (`await symbol.landing`) instead of stomping it, and can\n * tell a symbol that already landed from one that still needs\n * `playLanding()`.\n *\n * Subclasses report a landing through {@link trackLanding}.\n */\n get landing(): Promise<void> | null {\n return this._landing;\n }\n\n /**\n * Subclass helper: record `run` as this symbol's landing beat (see\n * {@link landing}) and return it. `SpineReelSymbol` wraps its landing\n * one-shot in it; a sprite symbol wraps its settle tween.\n */\n protected trackLanding(run: Promise<void>): Promise<void> {\n this._landing = run;\n return run;\n }\n\n /** @internal Called by the reel when it leaves rest; the landing is over. */\n resetLanding(): void {\n this._landing = null;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity. Called when the symbol enters\n * the visible reel or is recycled from the pool. Resets container\n * transform / filter state for parity with deactivate().\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // Tint is a mutable visual like the rest: a symbol arriving from the pool\n // must not wear a colour some previous cell put on it. Cleared on both\n // sides of the recycle so neither half has to trust the other.\n this.view.tint = 0xffffff;\n this.view.zIndex = 0;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool. Stops\n * animations, hides the view, and resets container transform / filter\n * state so subclass decorations don't leak across recycles.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this._landing = null;\n this.view.visible = false;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // See activate(). A symbol dimmed by the board (or tinted by a game) is\n // released to the pool the moment its cell swaps, and the pool hands the\n // same instance to a different cell - which would inherit the colour.\n this.view.tint = 0xffffff;\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 // A pooled symbol's view stays a child of the reel container it was\n // released from, so `Reel.destroy()` has already destroyed it (children\n // included) by the time the pool disposes the symbol. There is nothing\n // left to stop or clean visually, and a `stopAnimation()` that reaches\n // into a destroyed child (`CardSymbol` resets its label's scale) throws.\n if (!this.view.destroyed) {\n this.stopAnimation();\n this.onDeactivate();\n }\n this.onDestroy();\n if (!this.view.destroyed) this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {}\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /**\n * Play the landing beat for this symbol - the settle a symbol does as its\n * reel stops, before (or instead of) any celebration. Resolves when done.\n *\n * The reel engine never calls this itself: `onReelLanded()` is the\n * engine-driven hook, fired on every landed symbol. This is the explicit\n * counterpart for a caller that lands cells one at a time - `HoldAndWinBoard`\n * calls it on each lock when built with `lockAnimation('landing')`.\n *\n * Default: resolves at once. `SpineReelSymbol` overrides it with the\n * skeleton's `landing` track; a sprite symbol can override it with a tween.\n */\n async playLanding(): Promise<void> {}\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Play the cascade-destruction animation for this symbol. Called by\n * consumers (typically via `reelSet.destroySymbols(...)`) to disintegrate\n * a winning cell before the next cascade refill drops fresh symbols in.\n *\n * Override in subclasses for art-appropriate destruction, e.g. a Spine\n * symbol can play its `disintegration` track here, or a sprite symbol can\n * swap to a shatter atlas. The promise must resolve when the symbol is no\n * longer visible.\n *\n * Default: a snappy \"poof\" centered on the symbol's bounds regardless of\n * the view's anchor. Tiny anticipation pop (~60 ms) then a fast implode to\n * `scale: 0` + `alpha: 0` (~140 ms), ~200 ms total, no rotation. Reads\n * cleanly under win-cluster pacing without competing with the win\n * presenter. The view is left at `alpha: 0` (destroyed); position / pivot\n * are restored so pool reuse via `_replaceSymbol`'s same-id fast path\n * doesn't inherit a stale pivot offset.\n *\n * `opts.delay`. seconds to wait before the animation starts. Use to\n * stagger a cluster of winners (e.g. `i * 0.015`).\n * `opts.signal`. abort signal. If aborted (now or mid-animation), the\n * tween is killed and the view is snapped to its destroyed pose\n * (`alpha: 0`, transform restored). The promise resolves normally. abort\n * means \"skip to the end,\" not \"fail\". Subclasses that override this\n * method MUST honor the signal or document why they can't (e.g. a Spine\n * `disintegration` track is uninterruptible).\n */\n async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n const view = this.view;\n // Capture original transform so pool reuse sees a clean state.\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n // Pivot to bounds-center so the scale collapses around the visual\n // centre instead of the view's (0,0) corner. and compensate position\n // so the symbol doesn't visibly jump when the pivot moves.\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n // Moving the pivot moves the view by `delta * scale`, not by `delta`: a\n // container renders a local point at `position + (point - pivot) * scale`.\n // Scale is 1 on a plain reel, which is why dropping it went unnoticed, but\n // a curved reel scales every cell and the symbol would jump on destroy.\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n const snapDestroyed = (): void => {\n view.alpha = 0;\n view.scale.set(0, 0);\n };\n\n // Pre-abort: skip the tween entirely and snap to the destroyed pose.\n if (signal?.aborted) {\n snapDestroyed();\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n view.alpha = 0;\n return;\n }\n\n await new Promise<void>((resolve) => {\n const tl = this.gsap\n .timeline({ onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, delay })\n // Brief anticipation pop. small upscale, ~60 ms, with overshoot\n // so the implode reads as a release. No rotation.\n .to(view.scale, { x: 1.1, y: 1.1, duration: 0.06, ease: 'back.out(2.5)' })\n // Snap implode. scale -> 0 + alpha -> 0 together, snappy ease-in\n // so the symbol collapses into the cell centre and is gone.\n .to(view.scale, { x: 0, y: 0, duration: 0.14, ease: 'power3.in' }, '<+=0.04')\n .to(view, { alpha: 0, duration: 0.14, ease: 'power3.in' }, '<');\n\n const onAbort = (): void => {\n tl.kill();\n snapDestroyed();\n resolve();\n };\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Restore transform. alpha stays 0 (the symbol IS destroyed). Scale\n // restored to 1 so pool reuse via `_replaceSymbol`'s same-id fast path\n // doesn't inherit a stale 0× scale; _replaceSymbol also resets scale\n // explicitly but a defensive restore here makes the destroyed cell\n // observably \"ready to be re-skinned\" between calls.\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n }\n\n /**\n * Animate this symbol AWAY, without destroying it.\n *\n * The counterpart to {@link playIn}, and the seam a mystery reveal needs:\n * the cells on screen dissolve, their identities are swapped underneath, and\n * the new ones arrive. `playDestroy` is the cascade's version of the same\n * beat and is deliberately separate - it is tuned as a \"this cell was a\n * winner and is being consumed\" poof, and a reveal is not that.\n *\n * Leaves the view hidden (`alpha: 0`) with its transform restored, so the\n * caller may swap the identity and call `playIn` next.\n *\n * Override for art-appropriate exits - a Spine symbol plays its own `out`\n * track here. Honour `opts.signal`: abort means \"snap to the end\", not\n * \"fail\", so the promise still resolves.\n *\n * Default: a ~180 ms shrink-and-fade with no overshoot, centred on the\n * symbol's bounds rather than the view origin.\n */\n async playOut(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('out', opts);\n }\n\n /**\n * Animate this symbol IN, from nothing to its resting pose.\n *\n * Owns its own start pose. A symbol that has just been re-activated is fully\n * visible (`activate()` resets alpha and scale), so an entrance that assumed\n * it started hidden would pop before it animated. This sets the hidden pose\n * first, then plays.\n *\n * Override for art-appropriate entrances - a Spine symbol plays its own `in`\n * track here. Honour `opts.signal`: abort snaps to the RESTING pose (alpha 1,\n * scale 1), because arriving is what the caller asked for.\n *\n * Default: a ~200 ms fade and scale-up with a small overshoot.\n */\n async playIn(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('in', opts);\n }\n\n /**\n * Shared body of {@link playIn} / {@link playOut}.\n *\n * One function because the two are the same tween read in opposite\n * directions, and because the fiddly part - pivoting to the visual centre so\n * the scale does not collapse toward the view's (0, 0) corner, and\n * compensating the position for the pivot move - is identical and easy to get\n * subtly wrong twice. Same correction as `playDestroy`: a container renders a\n * local point at `position + (point - pivot) * scale`, so a curved reel (which\n * scales every cell) would make the symbol jump without the `* scale` term.\n */\n private async _tweenPresence(\n direction: 'in' | 'out',\n opts?: { delay?: number; signal?: AbortSignal },\n ): Promise<void> {\n const view = this.view;\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const restorePivot = (): void => {\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n };\n const snapEnd = (): void => {\n if (direction === 'out') {\n view.alpha = 0;\n view.scale.set(1, 1);\n } else {\n view.visible = true;\n view.alpha = 1;\n view.scale.set(1, 1);\n }\n };\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n if (direction === 'in') {\n // Own the start pose: `activate()` leaves a freshly swapped symbol fully\n // visible, so without this the new art is on screen for a frame before\n // the entrance begins.\n view.visible = true;\n view.alpha = 0;\n view.scale.set(0.72, 0.72);\n }\n\n if (signal?.aborted) {\n snapEnd();\n restorePivot();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => {\n tl.kill();\n snapEnd();\n resolve();\n };\n const tl = this.gsap.timeline({\n delay,\n onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n },\n });\n if (direction === 'out') {\n tl.to(view.scale, { x: 0.72, y: 0.72, duration: 0.18, ease: 'power2.in' }).to(\n view,\n { alpha: 0, duration: 0.18, ease: 'power2.in' },\n '<',\n );\n } else {\n tl.to(view.scale, { x: 1, y: 1, duration: 0.2, ease: 'back.out(1.7)' }).to(\n view,\n { alpha: 1, duration: 0.16, ease: 'power2.out' },\n '<',\n );\n }\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Scale back to 1 either way: a hidden symbol still has to be a legal\n // resting pose, or pool reuse inherits the shrunken scale.\n view.scale.set(1, 1);\n restorePivot();\n }\n\n /**\n * The owning reel is curved: render into this projected quad instead of the\n * flat cell rectangle. `null` means the reel is flat again.\n *\n * The quad's corners are SCREEN-space and local to this view's own origin,\n * clockwise from top-left, and `width` / `height` give the flat cell box the\n * quad replaces. Called on every frame of motion and on every placement, so\n * it must be cheap and idempotent.\n *\n * **The default is an approximation.** A `Container` transform is affine, so\n * it cannot express a trapezoid; the base class fits the quad as closely as\n * an affine transform can - a UNIFORM scale about the quad's centre. Uniform\n * on purpose: symbol art is usually smaller than its cell and has a shape a\n * player recognises, and squashing one axis turns a `7` into a squashed `7`\n * rather than a `7` seen at an angle.\n *\n * Override this to render the real perspective. {@link SpriteSymbol} and\n * {@link AnimatedSpriteSymbol} do, by drawing their texture through a\n * `PerspectiveMesh`, which costs no extra render pass because the content is\n * already a texture. A symbol whose content is an arbitrary subtree (Spine, a\n * composite of sprites and text) cannot do that without rendering itself to a\n * texture every frame, so it keeps the affine fit.\n *\n * Whatever you do here, do NOT move `view.position`: the reel reads that\n * coordinate back to work out which slot this symbol is in.\n */\n /**\n * The part of its cell this symbol's art actually covers, or `null` (the\n * default) for \"all of it\".\n *\n * Slot art is usually smaller than its cell - a trimmed atlas frame is a\n * shape floating in a much bigger transparent box - and the reel needs to\n * know that to project the rectangle the art is really in. Overriding this\n * is what stops a small symbol being inflated to the cell's edges and given\n * the cell's keystone instead of its own, milder one.\n *\n * Read once per projection, so it may change with the symbol's identity.\n */\n get cellInset(): ReelCellInset | null {\n return null;\n }\n\n applyCellQuad(quad: ReelCellQuad | null): void {\n const view = this.view;\n if (quad === null) {\n view.scale.set(1, 1);\n view.pivot.set(0, 0);\n return;\n }\n // Measure the trapezoid: the mean of its two parallel edges, and the\n // distance between their midpoints.\n const nearWidth = Math.hypot(quad.x1 - quad.x0, quad.y1 - quad.y0);\n const farWidth = Math.hypot(quad.x2 - quad.x3, quad.y2 - quad.y3);\n const across = (nearWidth + farWidth) / 2;\n const along = Math.hypot(\n (quad.x3 + quad.x2) / 2 - (quad.x0 + quad.x1) / 2,\n (quad.y3 + quad.y2) / 2 - (quad.y0 + quad.y1) / 2,\n );\n // CONTAIN, not cover. A quad in the middle of the window is TALLER than\n // the flat cell (that is the drum magnifying what faces you), so a scale\n // picked to fill it would also make the symbol wider than its column and\n // overlap its neighbours - very visible on art that fills its cell\n // edge-to-edge. Taking the smaller ratio keeps every symbol inside its own\n // projected footprint at the cost of a little slack on one axis.\n const scale =\n quad.width > 0 && quad.height > 0\n ? Math.min(across / quad.width, along / quad.height)\n : 1;\n\n const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n view.scale.set(scale, scale);\n // A container renders a local point at `position + (point - pivot) * scale`\n // and `position` must not move, so putting the flat box's centre on the\n // quad's centre has to be paid for entirely out of the pivot.\n view.pivot.set(\n quad.x + quad.width / 2 - cx / scale,\n quad.y + quad.height / 2 - cy / scale,\n );\n }\n\n /**\n * Lifecycle hook: the owning reel is spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur,\n * StaticSpinSymbol) to swap to a spin presentation automatically.\n *\n * Fired on every strip symbol (visible AND buffer cells) when the reel\n * enters the spin phase, and again with `joinedMidSpin: true` on each\n * symbol freshly installed while the reel is already spinning (pool\n * recycling wipes symbol state, so a wrapped-in symbol can't know the\n * reel is moving without this). Implementations MUST be idempotent.\n * the same instance can be notified more than once per spin.\n *\n * @param joinedMidSpin true when this symbol was installed into a reel\n * already at speed (skip start-of-spin transitions like blur ramps).\n */\n onReelSpinStart(joinedMidSpin?: boolean): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel entered its anticipation (tease) phase.\n * it is still spinning, but slowed enough that the strip is readable.\n * Spin presentations that obscure symbols (blur textures, smear\n * animations) should relax so the player can follow the tease. Also\n * fired on symbols installed while the reel is anticipating.\n * Implementations MUST be idempotent. Default: no-op.\n */\n onReelAnticipationStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n *\n * `ctx` says which reel and cell this symbol landed in, so an override can\n * play a different beat - or none - on a particular reel. The engine always\n * supplies it; it is optional only so an override written as\n * `onReelLanded()` keeps compiling. Fired before the reel's `landing` event\n * and the set's `spin:reelLanding`, by contract.\n */\n onReelLanded(ctx?: ReelLandingContext): void {}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./ReelSymbol-BvdqdXMY.js";
1
+ import { t as e } from "./ReelSymbol-C7Pd7BeO.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-C6FYNcwP.js.map
70
+ //# sourceMappingURL=SpineSymbol-CSTDCL4W.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-C6FYNcwP.js","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":";;AAGA,IAAI,IAAkB,MAMhB,KAA6B,YAAY;AAC7C,KAAI;AAEF,OADoB,MAAM,OAAO,oCACR;SACnB;IAGN;AAQJ,SAAgB,IAAgC;AAC9C,QAAO;;AA2BT,IAAa,IAAb,cAAiC,EAAW;CAC1C,SAAsB;CACtB;CACA;CACA;CACA;CACA,cAA2C;CAC3C,sBAAsC;CACtC;CAEA,YAAY,GAA6B;AAEvC,MADA,OAAO,EACH,CAAC,EACH,OAAU,MACR,6PAID;AAMH,EAJA,KAAK,mBAAmB,EAAQ,iBAChC,KAAK,iBAAiB,EAAQ,iBAAiB,QAC/C,KAAK,gBAAgB,EAAQ,gBAAgB,OAC7C,KAAK,eAAe,EAAQ,eAAe,WAC3C,KAAK,UAAU,EAAQ,UAAU;;CAGnC,WAAqB,GAAwB;EAC3C,IAAM,IAAe,KAAK,iBAAiB;AACtC,SAiBL;OAdI,KAAK,wBAAwB,MAC3B,KAAK,WACP,KAAK,KAAK,YAAY,KAAK,OAAO,EAClC,KAAK,OAAO,SAAS,GAEvB,KAAK,SAAS,IAAI,EAAW,EAAE,iBAAc,CAAC,EAC9C,KAAK,KAAK,SAAS,KAAK,OAAO,EAC/B,KAAK,sBAAsB,IAGzB,KAAK,OAAO,SAAS,KAAK,SAAS,KAAK,aAAa,KACvD,KAAK,OAAO,SAAS,cAAc,KAAK,aAAa,EACrD,KAAK,OAAO,SAAS,qBAAqB,GAExC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,CAC9D,MAAK,OAAO,MAAM,aAAa,GAAG,KAAK,gBAAgB,GAAK;YACnD,KAAK,QACd,OAAU,MACR,wCAAwC,KAAK,eAAe,2BAC5C,EAAS,IAC1B;;;CAIL,eAA+B;AAO7B,MANI,KAAK,WACP,KAAK,OAAO,MAAM,gBAAgB,EAClC,KAAK,OAAO,MAAM,aAAa,GAI7B,KAAK,aAAa;GACpB,IAAM,IAAI,KAAK;AAEf,GADA,KAAK,cAAc,MACnB,GAAG;;;CAIP,MAAM,UAAyB;AACxB,WAAK,QACV;OAAI,CAAC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,EAAE;AAChE,QAAI,KAAK,QACP,OAAU,MACR,uCAAuC,KAAK,cAAc,2BAC1C,KAAK,oBAAoB,IAC1C;AAEH;;AAGF,UAAO,IAAI,SAAe,MAAY;AACpC,SAAK,cAAc;IACnB,IAAM,IAAQ,KAAK,OAAO,MAAM,aAAa,GAAG,KAAK,eAAe,GAAM;AAC1E,SAAK,OAAO,MAAM,YAAY,EAC5B,WAAW,MAAoB;AAC7B,KAAI,MAAe,MACjB,KAAK,OAAO,MAAM,gBAAgB,EAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,IAC9D,KAAK,OAAO,MAAM,aAAa,GAAG,KAAK,gBAAgB,GAAK,EAE9D,KAAK,cAAc,MACnB,GAAS;OAGd,CAAC;KACF;;;CAGJ,gBAAsB;AACf,OAAK,WACV,KAAK,OAAO,MAAM,gBAAgB,EAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,IAC9D,KAAK,OAAO,MAAM,aAAa,GAAG,KAAK,gBAAgB,GAAK,EAE9D,AAEE,KAAK,iBADL,KAAK,aAAa,EACC;;CAIvB,OAAO,GAAe,GAAsB;AAC1C,MAAI,CAAC,KAAK,OAAQ;EAClB,IAAM,IAAS,KAAK,OAAO,WAAW;AACtC,EAAI,EAAO,QAAQ,KAAK,EAAO,SAAS,KACtC,KAAK,OAAO,MAAM,IAChB,IAAQ,EAAO,OACf,IAAS,EAAO,OACjB;;CAIL,YAAqC;AAMnC,MALA,AAGE,KAAK,YAFL,KAAK,OAAO,MAAM,gBAAgB,EAClC,KAAK,OAAO,SAAS,EACP,OAEZ,KAAK,aAAa;GACpB,IAAM,IAAI,KAAK;AAEf,GADA,KAAK,cAAc,MACnB,GAAG"}
1
+ {"version":3,"file":"SpineSymbol-CSTDCL4W.js","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./ReelSymbol-CTlZ9ihf.cjs`);var t=null,n=(async()=>{try{t=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}})();function r(){return n}var i=class extends e.t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;_strict;constructor(e){if(super(),!t)throw Error(`SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is not installed (npm install @esotericsoftware/spine-pixi-v8), or its dynamic import has not resolved yet on a cold start — await whenSpineReady() before constructing SpineSymbols.`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`,this._strict=e.strict??!1}onActivate(e){let n=this._skeletonDataMap[e];if(n){if(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new t({skeletonData:n}),this.view.addChild(this._spine),this._currentSkeletonKey=e),this._spine.skeleton.data.findSkin(this._defaultSkin)&&(this._spine.skeleton.setSkinByName(this._defaultSkin),this._spine.skeleton.setSlotsToSetupPose()),this._spine.skeleton.data.findAnimation(this._idleAnimation))this._spine.state.setAnimation(0,this._idleAnimation,!0);else if(this._strict)throw Error(`SpineSymbol(strict): idle animation '${this._idleAnimation}' not found on skeleton '${e}'.`)}}onDeactivate(){if(this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}async playWin(){if(this._spine){if(!this._spine.skeleton.data.findAnimation(this._winAnimation)){if(this._strict)throw Error(`SpineSymbol(strict): win animation '${this._winAnimation}' not found on skeleton '${this._currentSkeletonKey}'.`);return}return new Promise(e=>{this._winResolve=e;let t=this._spine.state.setAnimation(0,this._winAnimation,!1);this._spine.state.addListener({complete:n=>{n===t&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve=null,e())}})})}}stopAnimation(){this._spine&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve&&=(this._winResolve(),null))}resize(e,t){if(!this._spine)return;let n=this._spine.getBounds();n.width>0&&n.height>0&&this._spine.scale.set(e/n.width,t/n.height)}onDestroy(){if(this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
2
- //# sourceMappingURL=SpineSymbol-Do0zw5ad.cjs.map
1
+ const e=require("./ReelSymbol-B5QBhkmw.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-DorL_xII.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-Do0zw5ad.cjs","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"6CAGA,IAAI,EAAkB,KAMhB,GAA6B,SAAY,CAC7C,GAAI,CAEF,GADoB,MAAM,OAAO,oCACR,WACnB,MAGN,CAQJ,SAAgB,GAAgC,CAC9C,OAAO,EA2BT,IAAa,EAAb,cAAiC,EAAA,CAAW,CAC1C,OAAsB,KACtB,iBACA,eACA,cACA,aACA,YAA2C,KAC3C,oBAAsC,GACtC,QAEA,YAAY,EAA6B,CAEvC,GADA,OAAO,CACH,CAAC,EACH,MAAU,MACR,6PAID,CAEH,KAAK,iBAAmB,EAAQ,gBAChC,KAAK,eAAiB,EAAQ,eAAiB,OAC/C,KAAK,cAAgB,EAAQ,cAAgB,MAC7C,KAAK,aAAe,EAAQ,aAAe,UAC3C,KAAK,QAAU,EAAQ,QAAU,GAGnC,WAAqB,EAAwB,CAC3C,IAAM,EAAe,KAAK,iBAAiB,GACtC,KAiBL,IAdI,KAAK,sBAAwB,IAC3B,KAAK,SACP,KAAK,KAAK,YAAY,KAAK,OAAO,CAClC,KAAK,OAAO,SAAS,EAEvB,KAAK,OAAS,IAAI,EAAW,CAAE,eAAc,CAAC,CAC9C,KAAK,KAAK,SAAS,KAAK,OAAO,CAC/B,KAAK,oBAAsB,GAGzB,KAAK,OAAO,SAAS,KAAK,SAAS,KAAK,aAAa,GACvD,KAAK,OAAO,SAAS,cAAc,KAAK,aAAa,CACrD,KAAK,OAAO,SAAS,qBAAqB,EAExC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,CAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,SACnD,KAAK,QACd,MAAU,MACR,wCAAwC,KAAK,eAAe,2BAC5C,EAAS,IAC1B,EAIL,cAA+B,CAO7B,GANI,KAAK,SACP,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,MAAM,aAAa,EAI7B,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG,EAIP,MAAM,SAAyB,CACxB,QAAK,OACV,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,CAAE,CAChE,GAAI,KAAK,QACP,MAAU,MACR,uCAAuC,KAAK,cAAc,2BAC1C,KAAK,oBAAoB,IAC1C,CAEH,OAGF,OAAO,IAAI,QAAe,GAAY,CACpC,KAAK,YAAc,EACnB,IAAM,EAAQ,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,cAAe,GAAM,CAC1E,KAAK,OAAO,MAAM,YAAY,CAC5B,SAAW,GAAoB,CACzB,IAAe,IACjB,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,KAAK,YAAc,KACnB,GAAS,GAGd,CAAC,EACF,EAGJ,eAAsB,CACf,KAAK,SACV,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,AAEE,KAAK,eADL,KAAK,aAAa,CACC,OAIvB,OAAO,EAAe,EAAsB,CAC1C,GAAI,CAAC,KAAK,OAAQ,OAClB,IAAM,EAAS,KAAK,OAAO,WAAW,CAClC,EAAO,MAAQ,GAAK,EAAO,OAAS,GACtC,KAAK,OAAO,MAAM,IAChB,EAAQ,EAAO,MACf,EAAS,EAAO,OACjB,CAIL,WAAqC,CAMnC,GALA,AAGE,KAAK,UAFL,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,SAAS,CACP,MAEZ,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG"}
1
+ {"version":3,"file":"SpineSymbol-DorL_xII.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":"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"}