pixi-reels 0.1.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 (103) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +167 -0
  3. package/dist/SpineSymbol-B40TevSr.cjs +2 -0
  4. package/dist/SpineSymbol-B40TevSr.cjs.map +1 -0
  5. package/dist/SpineSymbol-D2jhCzFW.js +82 -0
  6. package/dist/SpineSymbol-D2jhCzFW.js.map +1 -0
  7. package/dist/config/SpeedPresets.d.ts +49 -0
  8. package/dist/config/SpeedPresets.d.ts.map +1 -0
  9. package/dist/config/defaults.d.ts +12 -0
  10. package/dist/config/defaults.d.ts.map +1 -0
  11. package/dist/config/types.d.ts +93 -0
  12. package/dist/config/types.d.ts.map +1 -0
  13. package/dist/core/Reel.d.ts +106 -0
  14. package/dist/core/Reel.d.ts.map +1 -0
  15. package/dist/core/ReelMotion.d.ts +43 -0
  16. package/dist/core/ReelMotion.d.ts.map +1 -0
  17. package/dist/core/ReelSet.d.ts +101 -0
  18. package/dist/core/ReelSet.d.ts.map +1 -0
  19. package/dist/core/ReelSetBuilder.d.ts +102 -0
  20. package/dist/core/ReelSetBuilder.d.ts.map +1 -0
  21. package/dist/core/ReelViewport.d.ts +43 -0
  22. package/dist/core/ReelViewport.d.ts.map +1 -0
  23. package/dist/core/StopSequencer.d.ts +26 -0
  24. package/dist/core/StopSequencer.d.ts.map +1 -0
  25. package/dist/debug/debug.d.ts +65 -0
  26. package/dist/debug/debug.d.ts.map +1 -0
  27. package/dist/events/EventEmitter.d.ts +25 -0
  28. package/dist/events/EventEmitter.d.ts.map +1 -0
  29. package/dist/events/ReelEvents.d.ts +40 -0
  30. package/dist/events/ReelEvents.d.ts.map +1 -0
  31. package/dist/frame/FrameBuilder.d.ts +50 -0
  32. package/dist/frame/FrameBuilder.d.ts.map +1 -0
  33. package/dist/frame/OffsetCalculator.d.ts +19 -0
  34. package/dist/frame/OffsetCalculator.d.ts.map +1 -0
  35. package/dist/frame/RandomSymbolProvider.d.ts +27 -0
  36. package/dist/frame/RandomSymbolProvider.d.ts.map +1 -0
  37. package/dist/index.cjs +5 -0
  38. package/dist/index.cjs.map +1 -0
  39. package/dist/index.d.ts +51 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +1454 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/pool/ObjectPool.d.ts +36 -0
  44. package/dist/pool/ObjectPool.d.ts.map +1 -0
  45. package/dist/speed/SpeedManager.d.ts +38 -0
  46. package/dist/speed/SpeedManager.d.ts.map +1 -0
  47. package/dist/spin/SpinController.d.ts +71 -0
  48. package/dist/spin/SpinController.d.ts.map +1 -0
  49. package/dist/spin/modes/CascadeMode.d.ts +16 -0
  50. package/dist/spin/modes/CascadeMode.d.ts.map +1 -0
  51. package/dist/spin/modes/ImmediateMode.d.ts +10 -0
  52. package/dist/spin/modes/ImmediateMode.d.ts.map +1 -0
  53. package/dist/spin/modes/SpinningMode.d.ts +18 -0
  54. package/dist/spin/modes/SpinningMode.d.ts.map +1 -0
  55. package/dist/spin/modes/StandardMode.d.ts +10 -0
  56. package/dist/spin/modes/StandardMode.d.ts.map +1 -0
  57. package/dist/spin/phases/AnticipationPhase.d.ts +24 -0
  58. package/dist/spin/phases/AnticipationPhase.d.ts.map +1 -0
  59. package/dist/spin/phases/PhaseFactory.d.ts +21 -0
  60. package/dist/spin/phases/PhaseFactory.d.ts.map +1 -0
  61. package/dist/spin/phases/ReelPhase.d.ts +39 -0
  62. package/dist/spin/phases/ReelPhase.d.ts.map +1 -0
  63. package/dist/spin/phases/SpinPhase.d.ts +25 -0
  64. package/dist/spin/phases/SpinPhase.d.ts.map +1 -0
  65. package/dist/spin/phases/StartPhase.d.ts +26 -0
  66. package/dist/spin/phases/StartPhase.d.ts.map +1 -0
  67. package/dist/spin/phases/StopPhase.d.ts +37 -0
  68. package/dist/spin/phases/StopPhase.d.ts.map +1 -0
  69. package/dist/spine/SpineReelSymbol.d.ts +111 -0
  70. package/dist/spine/SpineReelSymbol.d.ts.map +1 -0
  71. package/dist/spine/index.d.ts +5 -0
  72. package/dist/spine/index.d.ts.map +1 -0
  73. package/dist/spine.cjs +2 -0
  74. package/dist/spine.cjs.map +1 -0
  75. package/dist/spine.js +123 -0
  76. package/dist/spine.js.map +1 -0
  77. package/dist/spotlight/SymbolSpotlight.d.ts +70 -0
  78. package/dist/spotlight/SymbolSpotlight.d.ts.map +1 -0
  79. package/dist/symbols/AnimatedSpriteSymbol.d.ts +30 -0
  80. package/dist/symbols/AnimatedSpriteSymbol.d.ts.map +1 -0
  81. package/dist/symbols/ReelSymbol.d.ts +84 -0
  82. package/dist/symbols/ReelSymbol.d.ts.map +1 -0
  83. package/dist/symbols/SpineSymbol.d.ts +34 -0
  84. package/dist/symbols/SpineSymbol.d.ts.map +1 -0
  85. package/dist/symbols/SpriteSymbol.d.ts +29 -0
  86. package/dist/symbols/SpriteSymbol.d.ts.map +1 -0
  87. package/dist/symbols/SymbolFactory.d.ts +20 -0
  88. package/dist/symbols/SymbolFactory.d.ts.map +1 -0
  89. package/dist/symbols/SymbolRegistry.d.ts +25 -0
  90. package/dist/symbols/SymbolRegistry.d.ts.map +1 -0
  91. package/dist/testing/FakeTicker.d.ts +45 -0
  92. package/dist/testing/FakeTicker.d.ts.map +1 -0
  93. package/dist/testing/HeadlessSymbol.d.ts +28 -0
  94. package/dist/testing/HeadlessSymbol.d.ts.map +1 -0
  95. package/dist/testing/index.d.ts +5 -0
  96. package/dist/testing/index.d.ts.map +1 -0
  97. package/dist/testing/testHarness.d.ts +70 -0
  98. package/dist/testing/testHarness.d.ts.map +1 -0
  99. package/dist/utils/Disposable.d.ts +10 -0
  100. package/dist/utils/Disposable.d.ts.map +1 -0
  101. package/dist/utils/TickerRef.d.ts +30 -0
  102. package/dist/utils/TickerRef.d.ts.map +1 -0
  103. package/package.json +86 -0
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 schmooky and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ This repository bundles third-party runtimes and libraries (PixiJS, GSAP, the
24
+ Spine Runtime, and others) governed by their own license terms. See each
25
+ package's node_modules or upstream project for those terms. In particular, the
26
+ Spine Runtime (@esotericsoftware/spine-*) is licensed under the Spine Runtimes
27
+ License Agreement and requires users to hold a valid Spine Editor license when
28
+ integrating it into their own products.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # pixi-reels
2
+
3
+ [![npm version](https://img.shields.io/npm/v/pixi-reels?color=cb3837&logo=npm)](https://www.npmjs.com/package/pixi-reels)
4
+ [![npm downloads](https://img.shields.io/npm/dm/pixi-reels?color=cb3837&logo=npm)](https://www.npmjs.com/package/pixi-reels)
5
+ [![Bundle size](https://img.shields.io/bundlephobia/minzip/pixi-reels?label=gzip)](https://bundlephobia.com/package/pixi-reels)
6
+ [![CI](https://github.com/schmooky/pixi-reels/actions/workflows/ci.yml/badge.svg)](https://github.com/schmooky/pixi-reels/actions/workflows/ci.yml)
7
+ [![Release](https://github.com/schmooky/pixi-reels/actions/workflows/release.yml/badge.svg)](https://github.com/schmooky/pixi-reels/actions/workflows/release.yml)
8
+ [![CodeQL](https://github.com/schmooky/pixi-reels/actions/workflows/codeql.yml/badge.svg)](https://github.com/schmooky/pixi-reels/actions/workflows/codeql.yml)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
10
+ [![PixiJS v8](https://img.shields.io/badge/PixiJS-v8-e91e63)](https://pixijs.com/)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
+
13
+ A slot machine reel engine for [PixiJS v8](https://pixijs.com/). Fluent builder, typed events, and the weighty spin+stop feel modeled on real-money games — in about 35 kB gzipped.
14
+
15
+ ```bash
16
+ pnpm add pixi-reels pixi.js gsap
17
+ ```
18
+
19
+ ```ts
20
+ import { Application } from 'pixi.js';
21
+ import { ReelSetBuilder, SpriteSymbol, SpeedPresets } from 'pixi-reels';
22
+
23
+ const app = new Application();
24
+ await app.init({ width: 900, height: 540, background: '#0a0d14' });
25
+ document.body.appendChild(app.canvas);
26
+
27
+ const reelSet = new ReelSetBuilder()
28
+ .reels(5).visibleSymbols(3).symbolSize(140, 140)
29
+ .symbols((r) => {
30
+ r.register('cherry', SpriteSymbol, { textures: { cherry: cherryTex } });
31
+ r.register('seven', SpriteSymbol, { textures: { seven: sevenTex } });
32
+ r.register('bar', SpriteSymbol, { textures: { bar: barTex } });
33
+ })
34
+ .weights({ cherry: 40, seven: 10, bar: 20 })
35
+ .speed('normal', SpeedPresets.NORMAL)
36
+ .speed('turbo', SpeedPresets.TURBO)
37
+ .ticker(app.ticker)
38
+ .build();
39
+
40
+ app.stage.addChild(reelSet);
41
+
42
+ // Kick off the spin, tell it where to land, await the bounce.
43
+ const spin = reelSet.spin();
44
+ reelSet.setResult(await fetchSpinFromServer());
45
+ const { symbols } = await spin;
46
+ ```
47
+
48
+ ## What it does
49
+
50
+ - **Spin lifecycle** — `START -> SPIN -> ANTICIPATION -> STOP` phases, each pluggable.
51
+ - **Weighty stops** — the reel carries momentum through the target frame, snaps, then bounces. No floaty ease-in deceleration.
52
+ - **Speed modes** — Normal / Turbo / SuperTurbo built in, or register your own profile.
53
+ - **Skip / slam-stop** — second tap of the spin button immediately lands the reels on target.
54
+ - **Win spotlight** — dim non-winners, promote winning symbols above the mask, cycle lines.
55
+ - **Symbol plugins** — SpriteSymbol, AnimatedSpriteSymbol, SpineSymbol, or implement `ReelSymbol`.
56
+ - **Frame middleware** — intercept the symbol generator (e.g. "no triples", multiplier injection).
57
+ - **Object pooling** — zero-allocation spinning via `ObjectPool<T>`.
58
+ - **Typed events** — `spin:start`, `spin:reelLanded`, `speed:changed`, `spotlight:end`, ...
59
+ - **Headless testing** — `createTestReelSet` + `FakeTicker` run the full lifecycle in Node.
60
+ - **Debug mode** — `enableDebug(reelSet)` exposes JSON and ASCII snapshots on `window`.
61
+
62
+ ## Docs
63
+
64
+ The docs site lives in [`apps/site`](apps/site/). Run it locally:
65
+
66
+ ```bash
67
+ pnpm site:dev # http://localhost:4321
68
+ ```
69
+
70
+ You'll find:
71
+
72
+ - **Guides** — getting started, spin lifecycle, symbols, speed modes, win animations, debugging.
73
+ - **Recipes** — small how-tos for common mechanics (walking wilds, sticky wilds, cascade, mystery reveal, hold & win, ...). Each ships with a live mini-demo.
74
+ - **Demos** — full mechanic sandboxes with cheat panels. One click forces a scatter, a near-miss, a guaranteed jackpot.
75
+ - **Sandbox** — in-browser TypeScript playground; edit the file, hit Run, reels rebuild.
76
+ - **Wiki** — API reference.
77
+
78
+ ## Examples
79
+
80
+ Runnable apps in [`examples/`](examples/):
81
+
82
+ | Example | What it shows | Run |
83
+ |------------------|------------------------------------------------------------|----------------------------------------|
84
+ | `classic-spin` | 5x3 line-pay slot with Spine symbols and speed toggle | `pnpm --filter classic-spin dev` |
85
+ | `cascade-tumble` | 6x5 tumble mechanic with win spotlight between stages | `pnpm --filter cascade-tumble dev` |
86
+ | `hold-and-win` | 5x3 base game + respin bonus with locking coins | `pnpm --filter hold-and-win dev` |
87
+ | `sandbox` | Single editable TS file, HMR rebuild | `pnpm --filter sandbox dev` |
88
+
89
+ ## Core API at a glance
90
+
91
+ ```ts
92
+ reelSet.spin(): Promise<SpinResult> // Start spinning
93
+ reelSet.setResult(symbols: string[][]) // Pass the target grid (triggers the stop)
94
+ reelSet.setAnticipation([3, 4]) // Slow reels 3+4 before their landing
95
+ reelSet.setStopDelays([0, 140, 280, 600, 1100]) // Override per-reel stop stagger
96
+ reelSet.skip() // Slam-stop
97
+ reelSet.setSpeed('turbo') // Switch speed profile
98
+ reelSet.spotlight.show(positions, opts) // One-shot win highlight
99
+ reelSet.spotlight.cycle(lines, opts) // Cycle through win lines
100
+ reelSet.events.on('spin:reelLanded', (i, s) => {/* ... */})
101
+ reelSet.destroy() // Full teardown
102
+ ```
103
+
104
+ Full reference: `/wiki/` on the docs site.
105
+
106
+ ## Spine symbols (optional)
107
+
108
+ pixi-reels ships a Spine adapter on a separate subpath so the runtime tree-shakes out when you don't need it:
109
+
110
+ ```ts
111
+ import { SpineReelSymbol } from 'pixi-reels/spine';
112
+
113
+ r.register('wild', SpineReelSymbol, {
114
+ spineMap: { wild: { skeleton: 'wildData', atlas: 'myAtlas' } },
115
+ autoPlayBlur: true, // plays `blur` during spin
116
+ autoPlayLanding: true, // plays `landing` on reel stop
117
+ });
118
+ ```
119
+
120
+ Install the peer: `pnpm add @esotericsoftware/spine-pixi-v8`.
121
+
122
+ ## Debug mode
123
+
124
+ Handy for development and essential when an AI agent needs to inspect reel state without parsing a canvas:
125
+
126
+ ```ts
127
+ import { enableDebug } from 'pixi-reels';
128
+ enableDebug(reelSet);
129
+ ```
130
+
131
+ In the browser console (or via Playwright / an agent's `eval`):
132
+
133
+ ```
134
+ __PIXI_REELS_DEBUG.log() // ASCII grid + state snapshot
135
+ __PIXI_REELS_DEBUG.snapshot() // Full JSON state
136
+ __PIXI_REELS_DEBUG.trace() // Log every domain event as it fires
137
+ ```
138
+
139
+ ## Architecture
140
+
141
+ ```
142
+ ReelSetBuilder --builds--> ReelSet
143
+ |- SpinController ..... orchestrates the phases per reel
144
+ |- SpeedManager ....... named profiles + live switching
145
+ |- SymbolSpotlight .... win animations
146
+ |- ReelViewport ....... masked + unmasked containers
147
+ '- Reel[] ............. one per column
148
+ |- ReelSymbol[] .. SpriteSymbol / AnimatedSpriteSymbol / SpineSymbol
149
+ |- ReelMotion .... y displacement + wrap
150
+ '- StopSequencer . target-frame consumption
151
+ ```
152
+
153
+ Single ticker, no circular deps, no default exports, tree-shakes cleanly.
154
+
155
+ ## Peer dependencies
156
+
157
+ - `pixi.js` ^8.17.0
158
+ - `gsap` ^3.14.0
159
+ - `@esotericsoftware/spine-pixi-v8` ^4.2.108 _(optional — only if you use `SpineReelSymbol`)_
160
+
161
+ ## Contributing
162
+
163
+ PRs welcome. [CONTRIBUTING.md](./CONTRIBUTING.md) covers the workflow, changesets, and the handful of style rules the lint guards enforce.
164
+
165
+ ## License
166
+
167
+ MIT.
@@ -0,0 +1,2 @@
1
+ let e=require(`pixi.js`);var t=class{view;_symbolId=``;_isDestroyed=!1;constructor(){this.view=new e.Container}get symbolId(){return this._symbolId}get isDestroyed(){return this._isDestroyed}activate(e){this._symbolId=e,this.view.visible=!0,this.onActivate(e)}deactivate(){this.stopAnimation(),this.onDeactivate(),this._symbolId=``,this.view.visible=!1}reset(){this.deactivate()}destroy(){this._isDestroyed||=(this.deactivate(),this.onDestroy(),this.view.destroy({children:!0}),!0)}onDestroy(){}onReelSpinStart(){}onReelSpinEnd(){}onReelLanded(){}},n=null;async function r(){try{n=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}}r();var i=class extends t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;constructor(e){if(super(),!n)throw Error(`SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. Install it with: npm install @esotericsoftware/spine-pixi-v8`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`}onActivate(e){let t=this._skeletonDataMap[e];t&&(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new n({skeletonData:t}),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))}onDeactivate(){this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve=null}async playWin(){if(this._spine&&this._spine.skeleton.data.findAnimation(this._winAnimation))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(){this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null)}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
2
+ //# sourceMappingURL=SpineSymbol-B40TevSr.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpineSymbol-B40TevSr.cjs","names":[],"sources":["../src/symbols/ReelSymbol.ts","../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.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 constructor() {\n this.view = new Container();\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity.\n * Called when the symbol enters the visible reel or is recycled from the pool.\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool.\n * Stops any running animations and hides the view.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this.view.visible = false;\n }\n\n /** Pool reset — aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.deactivate();\n this.onDestroy();\n 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 // Override if needed\n }\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Lifecycle hook: the owning reel has started spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur) to swap to\n * a blur animation automatically.\n */\n onReelSpinStart(): 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 has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n */\n onReelLanded(): void {}\n}\n","import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically — this is an optional peer dependency\nlet SpineClass: any = null;\n\nasync function loadSpine(): Promise<void> {\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// Attempt to load Spine on module init\nloadSpine();\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\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\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. ' +\n 'Install it with: npm install @esotericsoftware/spine-pixi-v8',\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 }\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 // Set to idle\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 }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n this._winResolve = null;\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) return;\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 // Return to idle\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 }\n}\n"],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { Container as e } from "pixi.js";
2
+ //#region src/symbols/ReelSymbol.ts
3
+ var t = class {
4
+ view;
5
+ _symbolId = "";
6
+ _isDestroyed = !1;
7
+ constructor() {
8
+ this.view = new e();
9
+ }
10
+ get symbolId() {
11
+ return this._symbolId;
12
+ }
13
+ get isDestroyed() {
14
+ return this._isDestroyed;
15
+ }
16
+ activate(e) {
17
+ this._symbolId = e, this.view.visible = !0, this.onActivate(e);
18
+ }
19
+ deactivate() {
20
+ this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this.view.visible = !1;
21
+ }
22
+ reset() {
23
+ this.deactivate();
24
+ }
25
+ destroy() {
26
+ this._isDestroyed ||= (this.deactivate(), this.onDestroy(), this.view.destroy({ children: !0 }), !0);
27
+ }
28
+ onDestroy() {}
29
+ onReelSpinStart() {}
30
+ onReelSpinEnd() {}
31
+ onReelLanded() {}
32
+ }, n = null;
33
+ async function r() {
34
+ try {
35
+ n = (await import("@esotericsoftware/spine-pixi-v8")).Spine;
36
+ } catch {}
37
+ }
38
+ r();
39
+ var i = class extends t {
40
+ _spine = null;
41
+ _skeletonDataMap;
42
+ _idleAnimation;
43
+ _winAnimation;
44
+ _defaultSkin;
45
+ _winResolve = null;
46
+ _currentSkeletonKey = "";
47
+ constructor(e) {
48
+ if (super(), !n) throw Error("SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. Install it with: npm install @esotericsoftware/spine-pixi-v8");
49
+ this._skeletonDataMap = e.skeletonDataMap, this._idleAnimation = e.idleAnimation ?? "idle", this._winAnimation = e.winAnimation ?? "win", this._defaultSkin = e.defaultSkin ?? "default";
50
+ }
51
+ onActivate(e) {
52
+ let t = this._skeletonDataMap[e];
53
+ t && (this._currentSkeletonKey !== e && (this._spine && (this.view.removeChild(this._spine), this._spine.destroy()), this._spine = new n({ skeletonData: t }), 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));
54
+ }
55
+ onDeactivate() {
56
+ this._spine && (this._spine.state.clearListeners(), this._spine.state.clearTracks()), this._winResolve = null;
57
+ }
58
+ async playWin() {
59
+ if (this._spine && this._spine.skeleton.data.findAnimation(this._winAnimation)) return new Promise((e) => {
60
+ this._winResolve = e;
61
+ let t = this._spine.state.setAnimation(0, this._winAnimation, !1);
62
+ this._spine.state.addListener({ complete: (n) => {
63
+ 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());
64
+ } });
65
+ });
66
+ }
67
+ stopAnimation() {
68
+ 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));
69
+ }
70
+ resize(e, t) {
71
+ if (!this._spine) return;
72
+ let n = this._spine.getBounds();
73
+ n.width > 0 && n.height > 0 && this._spine.scale.set(e / n.width, t / n.height);
74
+ }
75
+ onDestroy() {
76
+ this._spine &&= (this._spine.state.clearListeners(), this._spine.destroy(), null);
77
+ }
78
+ };
79
+ //#endregion
80
+ export { t as n, i as t };
81
+
82
+ //# sourceMappingURL=SpineSymbol-D2jhCzFW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpineSymbol-D2jhCzFW.js","names":[],"sources":["../src/symbols/ReelSymbol.ts","../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.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 constructor() {\n this.view = new Container();\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity.\n * Called when the symbol enters the visible reel or is recycled from the pool.\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool.\n * Stops any running animations and hides the view.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this.view.visible = false;\n }\n\n /** Pool reset — aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.deactivate();\n this.onDestroy();\n 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 // Override if needed\n }\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Lifecycle hook: the owning reel has started spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur) to swap to\n * a blur animation automatically.\n */\n onReelSpinStart(): 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 has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n */\n onReelLanded(): void {}\n}\n","import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically — this is an optional peer dependency\nlet SpineClass: any = null;\n\nasync function loadSpine(): Promise<void> {\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// Attempt to load Spine on module init\nloadSpine();\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\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\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol requires @esotericsoftware/spine-pixi-v8 to be installed. ' +\n 'Install it with: npm install @esotericsoftware/spine-pixi-v8',\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 }\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 // Set to idle\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 }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n this._winResolve = null;\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) return;\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 // Return to idle\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 }\n}\n"],"mappings":";;AAiCA,IAAsB,IAAtB,MAAuD;CAErD;CAEA,YAA4B;CAC5B,eAAuB;CAEvB,cAAc;AACZ,OAAK,OAAO,IAAI,GAAW;;CAG7B,IAAI,WAAmB;AACrB,SAAO,KAAK;;CAGd,IAAI,cAAuB;AACzB,SAAO,KAAK;;CAOd,SAAS,GAAwB;AAG/B,EAFA,KAAK,YAAY,GACjB,KAAK,KAAK,UAAU,IACpB,KAAK,WAAW,EAAS;;CAO3B,aAAmB;AAIjB,EAHA,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,IACjB,KAAK,KAAK,UAAU;;CAItB,QAAc;AACZ,OAAK,YAAY;;CAGnB,UAAgB;AACV,EAIJ,KAAK,kBAHL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,KAAK,QAAQ,EAAE,UAAU,IAAM,CAAC,EACjB;;CAUtB,YAA4B;CAkB5B,kBAAwB;CAMxB,gBAAsB;CAOtB,eAAqB;GCzHnB,IAAkB;AAEtB,eAAe,IAA2B;AACxC,KAAI;AAEF,OADoB,MAAM,OAAO,oCACR;SACnB;;AAMV,GAAW;AAmBX,IAAa,IAAb,cAAiC,EAAW;CAC1C,SAAsB;CACtB;CACA;CACA;CACA;CACA,cAA2C;CAC3C,sBAAsC;CAEtC,YAAY,GAA6B;AAEvC,MADA,OAAO,EACH,CAAC,EACH,OAAU,MACR,qIAED;AAKH,EAHA,KAAK,mBAAmB,EAAQ,iBAChC,KAAK,iBAAiB,EAAQ,iBAAiB,QAC/C,KAAK,gBAAgB,EAAQ,gBAAgB,OAC7C,KAAK,eAAe,EAAQ,eAAe;;CAG7C,WAAqB,GAAwB;EAC3C,IAAM,IAAe,KAAK,iBAAiB;AACtC,QAGD,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,IAIzB,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,IAC9D,KAAK,OAAO,MAAM,aAAa,GAAG,KAAK,gBAAgB,GAAK;;CAIhE,eAA+B;AAK7B,EAJI,KAAK,WACP,KAAK,OAAO,MAAM,gBAAgB,EAClC,KAAK,OAAO,MAAM,aAAa,GAEjC,KAAK,cAAc;;CAGrB,MAAM,UAAyB;AACxB,WAAK,UACL,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,CAEhE,QAAO,IAAI,SAAe,MAAY;AACpC,QAAK,cAAc;GACnB,IAAM,IAAQ,KAAK,OAAO,MAAM,aAAa,GAAG,KAAK,eAAe,GAAM;AAC1E,QAAK,OAAO,MAAM,YAAY,EAC5B,WAAW,MAAoB;AAC7B,IAAI,MAAe,MACjB,KAAK,OAAO,MAAM,gBAAgB,EAE9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,IAC9D,KAAK,OAAO,MAAM,aAAa,GAAG,KAAK,gBAAgB,GAAK,EAE9D,KAAK,cAAc,MACnB,GAAS;MAGd,CAAC;IACF;;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;AACnC,EAGE,KAAK,YAFL,KAAK,OAAO,MAAM,gBAAgB,EAClC,KAAK,OAAO,SAAS,EACP"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Built-in speed profiles covering common slot game needs.
3
+ *
4
+ * Bounce values are tuned for the typical 120–200px symbol range. For larger
5
+ * or smaller symbols, register a custom profile or override `bounceDistance`.
6
+ * `bounceDuration` is the total time for the two-leg bounce (down then back).
7
+ */
8
+ export declare const SpeedPresets: {
9
+ readonly NORMAL: {
10
+ readonly name: "normal";
11
+ readonly spinDelay: 100;
12
+ readonly spinSpeed: 30;
13
+ readonly stopDelay: 140;
14
+ readonly anticipationDelay: 450;
15
+ readonly bounceDistance: 56;
16
+ readonly bounceDuration: 600;
17
+ readonly accelerationEase: "power2.in";
18
+ readonly decelerationEase: "power2.out";
19
+ readonly accelerationDuration: 300;
20
+ readonly minimumSpinTime: 500;
21
+ };
22
+ readonly TURBO: {
23
+ readonly name: "turbo";
24
+ readonly spinDelay: 30;
25
+ readonly spinSpeed: 50;
26
+ readonly stopDelay: 0;
27
+ readonly anticipationDelay: 250;
28
+ readonly bounceDistance: 42;
29
+ readonly bounceDuration: 200;
30
+ readonly accelerationEase: "power2.in";
31
+ readonly decelerationEase: "power2.out";
32
+ readonly accelerationDuration: 200;
33
+ readonly minimumSpinTime: 300;
34
+ };
35
+ readonly SUPER_TURBO: {
36
+ readonly name: "superTurbo";
37
+ readonly spinDelay: 0;
38
+ readonly spinSpeed: 80;
39
+ readonly stopDelay: 0;
40
+ readonly anticipationDelay: 0;
41
+ readonly bounceDistance: 14;
42
+ readonly bounceDuration: 120;
43
+ readonly accelerationEase: "power1.in";
44
+ readonly decelerationEase: "power1.out";
45
+ readonly accelerationDuration: 50;
46
+ readonly minimumSpinTime: 100;
47
+ };
48
+ };
49
+ //# sourceMappingURL=SpeedPresets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpeedPresets.d.ts","sourceRoot":"","sources":["../../src/config/SpeedPresets.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCwB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /** Default values applied when not explicitly set by the builder. */
2
+ export declare const DEFAULTS: {
3
+ bufferSymbols: number;
4
+ symbolGap: {
5
+ x: number;
6
+ y: number;
7
+ };
8
+ initialSpeed: string;
9
+ maxPoolPerKey: number;
10
+ zIndexStep: number;
11
+ };
12
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,eAAO,MAAM,QAAQ;mBACC,MAAM;;WACL,MAAM;WAAU,MAAM;;kBACjB,MAAM;mBACX,MAAM;gBACR,MAAM;CAC1B,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { Container, Ticker } from 'pixi.js';
2
+ /** Timing and animation profile for a speed mode. */
3
+ export interface SpeedProfile {
4
+ readonly name: string;
5
+ /** Milliseconds between each reel starting to spin. */
6
+ readonly spinDelay: number;
7
+ /** Pixels per frame at full spin speed. */
8
+ readonly spinSpeed: number;
9
+ /** Milliseconds between each reel stopping. */
10
+ readonly stopDelay: number;
11
+ /** Milliseconds to hold anticipation phase. */
12
+ readonly anticipationDelay: number;
13
+ /** Pixels of overshoot when reel lands. */
14
+ readonly bounceDistance: number;
15
+ /** Milliseconds for bounce-back animation. */
16
+ readonly bounceDuration: number;
17
+ /** Optional GSAP ease string for acceleration. Default: 'power2.in'. */
18
+ readonly accelerationEase?: string;
19
+ /** Optional GSAP ease string for deceleration. Default: 'power2.out'. */
20
+ readonly decelerationEase?: string;
21
+ /** Milliseconds for acceleration phase. Default: 300. */
22
+ readonly accelerationDuration?: number;
23
+ /** Minimum spin time in ms before stop is allowed. Default: 500. */
24
+ readonly minimumSpinTime?: number;
25
+ }
26
+ /** Per-symbol configuration data. */
27
+ export interface SymbolData {
28
+ /** Relative weight for random generation (higher = more frequent). */
29
+ weight: number;
30
+ /** Display layering order. Higher = in front. */
31
+ zIndex?: number;
32
+ /** If true, this symbol renders above the reel mask (for oversized animations). */
33
+ unmask?: boolean;
34
+ }
35
+ /** Configuration for the reel grid layout. */
36
+ export interface ReelGridConfig {
37
+ /** Number of reel columns. */
38
+ reelCount: number;
39
+ /** Number of visible symbol rows per reel. */
40
+ visibleRows: number;
41
+ /** Symbol width in pixels. */
42
+ symbolWidth: number;
43
+ /** Symbol height in pixels. */
44
+ symbolHeight: number;
45
+ /** Gap between symbols. Default: { x: 0, y: 0 }. */
46
+ symbolGap?: {
47
+ x: number;
48
+ y: number;
49
+ };
50
+ /** Number of buffer symbols above and below the visible area. Default: 1. */
51
+ bufferSymbols?: number;
52
+ }
53
+ /** Extra symbols above/below config per reel. */
54
+ export interface ReelExtraSymbols {
55
+ symbolsAbove: number;
56
+ symbolsBelow: number;
57
+ }
58
+ /** Offset modes for X-axis symbol positioning. */
59
+ export type OffsetXMode = 'none' | 'trapezoid';
60
+ /** Trapezoid perspective configuration. */
61
+ export interface TrapezoidConfig {
62
+ mode: 'trapezoid';
63
+ widthDifference: number;
64
+ topWidthFactor: number;
65
+ bottomWidthFactor: number;
66
+ }
67
+ /** No offset configuration. */
68
+ export interface NoOffsetConfig {
69
+ mode: 'none';
70
+ }
71
+ export type OffsetConfig = TrapezoidConfig | NoOffsetConfig;
72
+ /** 2D matrix type (reel × row). */
73
+ export type Matrix<T> = T[][];
74
+ /** Simple 2D position. */
75
+ export interface Position {
76
+ x: number;
77
+ y: number;
78
+ }
79
+ /** Mask configuration for the reel viewport. */
80
+ export interface MaskConfig {
81
+ mask: Container;
82
+ position: Position;
83
+ }
84
+ /** Full internal configuration assembled by the builder. */
85
+ export interface ReelSetInternalConfig {
86
+ grid: Required<ReelGridConfig>;
87
+ symbols: Record<string, SymbolData>;
88
+ speeds: Map<string, SpeedProfile>;
89
+ initialSpeed: string;
90
+ offset: OffsetConfig;
91
+ ticker: Ticker;
92
+ }
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { Container } from 'pixi.js';
2
+ import { Disposable } from '../utils/Disposable.js';
3
+ import { ReelSymbol } from '../symbols/ReelSymbol.js';
4
+ import { SymbolFactory } from '../symbols/SymbolFactory.js';
5
+ import { SymbolData } from '../config/types.js';
6
+ import { ReelMotion } from './ReelMotion.js';
7
+ import { StopSequencer } from './StopSequencer.js';
8
+ import { EventEmitter } from '../events/EventEmitter.js';
9
+ import { ReelEvents } from '../events/ReelEvents.js';
10
+ import { RandomSymbolProvider } from '../frame/RandomSymbolProvider.js';
11
+ import { ReelViewport } from './ReelViewport.js';
12
+ import { SpinningMode } from '../spin/modes/SpinningMode.js';
13
+ export interface ReelConfig {
14
+ reelIndex: number;
15
+ visibleRows: number;
16
+ bufferAbove: number;
17
+ bufferBelow: number;
18
+ symbolWidth: number;
19
+ symbolHeight: number;
20
+ symbolGapX: number;
21
+ symbolGapY: number;
22
+ symbolsData: Record<string, SymbolData>;
23
+ initialSymbols: string[];
24
+ }
25
+ /**
26
+ * One vertical column of a slot board.
27
+ *
28
+ * A `Reel` owns:
29
+ * - the `ReelSymbol[]` currently on screen (a small buffer above the
30
+ * visible rows + the visible rows + a small buffer below — so symbols
31
+ * can fade in from off-screen cleanly)
32
+ * - the `ReelMotion` that adds a Y delta each tick and wraps symbols
33
+ * that scroll off the ends
34
+ * - a `StopSequencer` — the queue of target symbols the reel still has
35
+ * to land on before it can stop
36
+ *
37
+ * You generally do not touch a `Reel` directly. Drive the `ReelSet` and
38
+ * let it fan out. Reels are exposed on `reelSet.reels` so you can read
39
+ * the current grid (`reel.getSymbolAt(row)`) or listen to per-reel
40
+ * events (`phase:enter`, `landed`, `symbol:created`, ...).
41
+ */
42
+ export declare class Reel implements Disposable {
43
+ readonly container: Container;
44
+ readonly events: EventEmitter<ReelEvents>;
45
+ readonly reelIndex: number;
46
+ /** Current symbols in order (top buffer → visible → bottom buffer). */
47
+ symbols: ReelSymbol[];
48
+ /** Current spin speed (pixels per frame). Set by phases. */
49
+ speed: number;
50
+ /** Current spinning mode. */
51
+ spinningMode: SpinningMode;
52
+ readonly motion: ReelMotion;
53
+ readonly stopSequencer: StopSequencer;
54
+ private _symbolFactory;
55
+ private _randomProvider;
56
+ private _viewport;
57
+ private _symbolsData;
58
+ private _visibleRows;
59
+ private _bufferAbove;
60
+ private _symbolWidth;
61
+ private _symbolHeight;
62
+ private _isDestroyed;
63
+ private _isStopping;
64
+ constructor(config: ReelConfig, symbolFactory: SymbolFactory, randomProvider: RandomSymbolProvider, viewport: ReelViewport);
65
+ get isDestroyed(): boolean;
66
+ get isStopping(): boolean;
67
+ set isStopping(value: boolean);
68
+ get bufferAbove(): number;
69
+ get bufferBelow(): number;
70
+ get visibleRows(): number;
71
+ /** Update reel for one frame. Called by SpinController via ticker. */
72
+ update(deltaMs: number): void;
73
+ /** Set the target frame for stopping. */
74
+ setStopFrame(frame: string[]): void;
75
+ /** Get visible symbol IDs (top to bottom, excluding buffers). */
76
+ getVisibleSymbols(): string[];
77
+ /** Get symbol at a visible row (0-indexed from top visible). */
78
+ getSymbolAt(visibleRow: number): ReelSymbol;
79
+ /** Notify all visible symbols that the reel has started spinning. */
80
+ notifySpinStart(): void;
81
+ /** Notify all visible symbols that the reel is about to stop (just before bounce). */
82
+ notifySpinEnd(): void;
83
+ /** Notify all visible symbols that the reel has landed on its target. */
84
+ notifyLanded(): void;
85
+ /** Snap all symbols to grid. */
86
+ snapToGrid(): void;
87
+ /** Place symbols immediately at target positions (for skip/turbo). */
88
+ placeSymbols(symbolIds: string[]): void;
89
+ /**
90
+ * Recompute `zIndex` for every symbol in the reel.
91
+ *
92
+ * Formula: `symbolData.zIndex ?? 0` (scaled by 100 to leave room for row
93
+ * ordering), plus the symbol's current array index — so bottom-row symbols
94
+ * render in front of top-row symbols and any symbol with a higher
95
+ * configured base zIndex (e.g. wild, bonus) renders above its neighbors.
96
+ *
97
+ * Called automatically after wraps, snaps, and direct placement. Call it
98
+ * manually after mutating `symbolsData.zIndex` at runtime.
99
+ */
100
+ refreshZIndex(): void;
101
+ destroy(): void;
102
+ private _setupSymbolPositions;
103
+ private _onSymbolWrapped;
104
+ private _replaceSymbol;
105
+ }
106
+ //# sourceMappingURL=Reel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Reel.d.ts","sourceRoot":"","sources":["../../src/core/Reel.ts"],"names":[],"mappings":"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"}