@slot-engine/core 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -61,6 +61,12 @@ interface GameConfigOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
61
61
  * Some required hooks must be implemented for certain features to work.
62
62
  */
63
63
  hooks: GameHooks<TGameModes, TSymbols, TUserState>;
64
+ /**
65
+ * If, for some reason, you run your game WITHOUT `cd`ing into the game root,\
66
+ * you can specify the root directory here to ensure assets are resolved correctly.\
67
+ * Normally, this is not needed.
68
+ */
69
+ rootDir?: string;
64
70
  }
65
71
  type GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> = Required<Omit<GameConfigOptions<TGameModes, TSymbols, TUserState>, "symbols">> & {
66
72
  /**
@@ -68,6 +74,7 @@ type GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends
68
74
  */
69
75
  symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>;
70
76
  outputDir: string;
77
+ rootDir: string;
71
78
  /**
72
79
  * A mapping of spin types to the number of scatter symbols required to trigger anticipation.
73
80
  */
@@ -1113,6 +1120,7 @@ declare class SlotGame<TGameModes extends AnyGameModes = AnyGameModes, TSymbols
1113
1120
  freespins: number;
1114
1121
  };
1115
1122
  outputDir: string;
1123
+ rootDir: string;
1116
1124
  id: string;
1117
1125
  name: string;
1118
1126
  gameModes: TGameModes;
@@ -1232,8 +1240,7 @@ type WinCombination = {
1232
1240
  posIndex: number;
1233
1241
  }>;
1234
1242
  };
1235
- type PostProcessFn<TWinCombs extends WinCombination[]> = (winType: WinType, ctx: GameContext) => {
1236
- payout: number;
1243
+ type PostProcessFn<TWinCombs extends WinCombination[]> = (wins: TWinCombs, ctx: GameContext) => {
1237
1244
  winCombinations: TWinCombs;
1238
1245
  };
1239
1246
  type WildSymbol = GameSymbol | Record<string, any>;
@@ -1305,8 +1312,6 @@ declare class ManywaysWinType extends WinType {
1305
1312
  payout: number;
1306
1313
  winCombinations: ManywaysWinCombination[];
1307
1314
  };
1308
- private _checked;
1309
- private _checkedWilds;
1310
1315
  constructor(opts: ManywaysWinTypeOpts);
1311
1316
  private validateConfig;
1312
1317
  /**
@@ -1315,8 +1320,6 @@ declare class ManywaysWinType extends WinType {
1315
1320
  */
1316
1321
  evaluateWins(board: Reels): this;
1317
1322
  private getWayLength;
1318
- private isChecked;
1319
- private isCheckedWild;
1320
1323
  }
1321
1324
  interface ManywaysWinTypeOpts extends WinTypeOpts {
1322
1325
  }
package/dist/index.d.ts CHANGED
@@ -61,6 +61,12 @@ interface GameConfigOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
61
61
  * Some required hooks must be implemented for certain features to work.
62
62
  */
63
63
  hooks: GameHooks<TGameModes, TSymbols, TUserState>;
64
+ /**
65
+ * If, for some reason, you run your game WITHOUT `cd`ing into the game root,\
66
+ * you can specify the root directory here to ensure assets are resolved correctly.\
67
+ * Normally, this is not needed.
68
+ */
69
+ rootDir?: string;
64
70
  }
65
71
  type GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> = Required<Omit<GameConfigOptions<TGameModes, TSymbols, TUserState>, "symbols">> & {
66
72
  /**
@@ -68,6 +74,7 @@ type GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends
68
74
  */
69
75
  symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>;
70
76
  outputDir: string;
77
+ rootDir: string;
71
78
  /**
72
79
  * A mapping of spin types to the number of scatter symbols required to trigger anticipation.
73
80
  */
@@ -1113,6 +1120,7 @@ declare class SlotGame<TGameModes extends AnyGameModes = AnyGameModes, TSymbols
1113
1120
  freespins: number;
1114
1121
  };
1115
1122
  outputDir: string;
1123
+ rootDir: string;
1116
1124
  id: string;
1117
1125
  name: string;
1118
1126
  gameModes: TGameModes;
@@ -1232,8 +1240,7 @@ type WinCombination = {
1232
1240
  posIndex: number;
1233
1241
  }>;
1234
1242
  };
1235
- type PostProcessFn<TWinCombs extends WinCombination[]> = (winType: WinType, ctx: GameContext) => {
1236
- payout: number;
1243
+ type PostProcessFn<TWinCombs extends WinCombination[]> = (wins: TWinCombs, ctx: GameContext) => {
1237
1244
  winCombinations: TWinCombs;
1238
1245
  };
1239
1246
  type WildSymbol = GameSymbol | Record<string, any>;
@@ -1305,8 +1312,6 @@ declare class ManywaysWinType extends WinType {
1305
1312
  payout: number;
1306
1313
  winCombinations: ManywaysWinCombination[];
1307
1314
  };
1308
- private _checked;
1309
- private _checkedWilds;
1310
1315
  constructor(opts: ManywaysWinTypeOpts);
1311
1316
  private validateConfig;
1312
1317
  /**
@@ -1315,8 +1320,6 @@ declare class ManywaysWinType extends WinType {
1315
1320
  */
1316
1321
  evaluateWins(board: Reels): this;
1317
1322
  private getWayLength;
1318
- private isChecked;
1319
- private isCheckedWild;
1320
1323
  }
1321
1324
  interface ManywaysWinTypeOpts extends WinTypeOpts {
1322
1325
  }
package/dist/index.js CHANGED
@@ -76,7 +76,8 @@ function createGameConfig(opts) {
76
76
  [SPIN_TYPE.BASE_GAME]: getAnticipationTrigger(SPIN_TYPE.BASE_GAME),
77
77
  [SPIN_TYPE.FREE_SPINS]: getAnticipationTrigger(SPIN_TYPE.FREE_SPINS)
78
78
  },
79
- outputDir: "__build__"
79
+ outputDir: "__build__",
80
+ rootDir: opts.rootDir || process.cwd()
80
81
  };
81
82
  }
82
83
 
@@ -1200,6 +1201,7 @@ var Book = class _Book {
1200
1201
  freespinsWins = 0;
1201
1202
  constructor(opts) {
1202
1203
  this.id = opts.id;
1204
+ this.criteria = opts.criteria;
1203
1205
  }
1204
1206
  /**
1205
1207
  * Intended for internal use only.
@@ -1521,10 +1523,14 @@ Simulating game mode: ${mode}`);
1521
1523
  const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
1522
1524
  await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
1523
1525
  createDirIfNotExists(
1524
- import_path.default.join(process.cwd(), this.gameConfig.outputDir, "optimization_files")
1526
+ import_path.default.join(
1527
+ this.gameConfig.rootDir,
1528
+ this.gameConfig.outputDir,
1529
+ "optimization_files"
1530
+ )
1525
1531
  );
1526
1532
  createDirIfNotExists(
1527
- import_path.default.join(process.cwd(), this.gameConfig.outputDir, "publish_files")
1533
+ import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "publish_files")
1528
1534
  );
1529
1535
  this.writeLookupTableCSV(mode);
1530
1536
  this.writeLookupTableSegmentedCSV(mode);
@@ -1577,7 +1583,7 @@ Simulating game mode: ${mode}`);
1577
1583
  await Promise.all(
1578
1584
  simRangesPerChunk.map(([simStart, simEnd], index) => {
1579
1585
  return this.callWorker({
1580
- basePath: this.gameConfig.outputDir,
1586
+ basePath: import_path.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir),
1581
1587
  mode,
1582
1588
  simStart,
1583
1589
  simEnd,
@@ -1600,7 +1606,7 @@ Simulating game mode: ${mode}`);
1600
1606
  }
1601
1607
  }
1602
1608
  return new Promise((resolve, reject) => {
1603
- const scriptPath = import_path.default.join(process.cwd(), basePath, TEMP_FILENAME);
1609
+ const scriptPath = import_path.default.join(basePath, TEMP_FILENAME);
1604
1610
  const worker = new import_worker_threads.Worker(scriptPath, {
1605
1611
  workerData: {
1606
1612
  mode,
@@ -1650,10 +1656,10 @@ Simulating game mode: ${mode}`);
1650
1656
  ctx.state.currentGameMode,
1651
1657
  criteria
1652
1658
  );
1659
+ ctx.state.currentResultSet = resultSet;
1653
1660
  while (!ctx.state.isCriteriaMet) {
1654
1661
  this.actualSims++;
1655
1662
  this.resetSimulation(ctx);
1656
- ctx.state.currentResultSet = resultSet;
1657
1663
  this.handleGameFlow(ctx);
1658
1664
  if (resultSet.meetsCriteria(ctx)) {
1659
1665
  ctx.state.isCriteriaMet = true;
@@ -1728,10 +1734,19 @@ Simulating game mode: ${mode}`);
1728
1734
  }
1729
1735
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
1730
1736
  let outputFileName = `lookUpTable_${gameMode}.csv`;
1731
- let outputFilePath = import_path.default.join(this.gameConfig.outputDir, outputFileName);
1737
+ let outputFilePath = import_path.default.join(
1738
+ this.gameConfig.rootDir,
1739
+ this.gameConfig.outputDir,
1740
+ outputFileName
1741
+ );
1732
1742
  writeFile(outputFilePath, rows.join("\n"));
1733
1743
  outputFileName = `lookUpTable_${gameMode}_0.csv`;
1734
- outputFilePath = import_path.default.join(this.gameConfig.outputDir, "publish_files", outputFileName);
1744
+ outputFilePath = import_path.default.join(
1745
+ this.gameConfig.rootDir,
1746
+ this.gameConfig.outputDir,
1747
+ "publish_files",
1748
+ outputFileName
1749
+ );
1735
1750
  writeFile(outputFilePath, rows.join("\n"));
1736
1751
  return outputFilePath;
1737
1752
  }
@@ -1745,20 +1760,28 @@ Simulating game mode: ${mode}`);
1745
1760
  }
1746
1761
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
1747
1762
  const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
1748
- const outputFilePath = import_path.default.join(this.gameConfig.outputDir, outputFileName);
1763
+ const outputFilePath = import_path.default.join(
1764
+ this.gameConfig.rootDir,
1765
+ this.gameConfig.outputDir,
1766
+ outputFileName
1767
+ );
1749
1768
  writeFile(outputFilePath, rows.join("\n"));
1750
1769
  return outputFilePath;
1751
1770
  }
1752
1771
  writeRecords(gameMode) {
1753
1772
  const outputFileName = `force_record_${gameMode}.json`;
1754
- const outputFilePath = import_path.default.join(this.gameConfig.outputDir, outputFileName);
1773
+ const outputFilePath = import_path.default.join(
1774
+ this.gameConfig.rootDir,
1775
+ this.gameConfig.outputDir,
1776
+ outputFileName
1777
+ );
1755
1778
  writeFile(outputFilePath, JSON.stringify(this.recorder.records, null, 2));
1756
1779
  if (this.debug) this.logSymbolOccurrences();
1757
1780
  return outputFilePath;
1758
1781
  }
1759
1782
  writeIndexJson() {
1760
1783
  const outputFilePath = import_path.default.join(
1761
- process.cwd(),
1784
+ this.gameConfig.rootDir,
1762
1785
  this.gameConfig.outputDir,
1763
1786
  "publish_files",
1764
1787
  "index.json"
@@ -1777,7 +1800,11 @@ Simulating game mode: ${mode}`);
1777
1800
  }
1778
1801
  async writeBooksJson(gameMode) {
1779
1802
  const outputFileName = `books_${gameMode}.jsonl`;
1780
- const outputFilePath = import_path.default.join(this.gameConfig.outputDir, outputFileName);
1803
+ const outputFilePath = import_path.default.join(
1804
+ this.gameConfig.rootDir,
1805
+ this.gameConfig.outputDir,
1806
+ outputFileName
1807
+ );
1781
1808
  const books = Array.from(this.library.values()).map((b) => b.serialize()).map((b) => ({
1782
1809
  id: b.id,
1783
1810
  payoutMultiplier: b.payout,
@@ -1787,7 +1814,7 @@ Simulating game mode: ${mode}`);
1787
1814
  writeFile(outputFilePath, contents);
1788
1815
  const compressedFileName = `books_${gameMode}.jsonl.zst`;
1789
1816
  const compressedFilePath = import_path.default.join(
1790
- process.cwd(),
1817
+ this.gameConfig.rootDir,
1791
1818
  this.gameConfig.outputDir,
1792
1819
  "publish_files",
1793
1820
  compressedFileName
@@ -1825,13 +1852,21 @@ Simulating game mode: ${mode}`);
1825
1852
  * Compiles user configured game to JS for use in different Node processes
1826
1853
  */
1827
1854
  preprocessFiles() {
1828
- const builtFilePath = import_path.default.join(this.gameConfig.outputDir, TEMP_FILENAME);
1855
+ const builtFilePath = import_path.default.join(
1856
+ this.gameConfig.rootDir,
1857
+ this.gameConfig.outputDir,
1858
+ TEMP_FILENAME
1859
+ );
1829
1860
  import_fs2.default.rmSync(builtFilePath, { force: true });
1830
1861
  (0, import_esbuild.buildSync)({
1831
- entryPoints: [process.cwd()],
1862
+ entryPoints: [this.gameConfig.rootDir],
1832
1863
  bundle: true,
1833
1864
  platform: "node",
1834
- outfile: import_path.default.join(this.gameConfig.outputDir, TEMP_FILENAME),
1865
+ outfile: import_path.default.join(
1866
+ this.gameConfig.rootDir,
1867
+ this.gameConfig.outputDir,
1868
+ TEMP_FILENAME
1869
+ ),
1835
1870
  external: ["esbuild"]
1836
1871
  });
1837
1872
  }
@@ -1942,6 +1977,18 @@ function parseLookupTable(content) {
1942
1977
  }
1943
1978
  return lut;
1944
1979
  }
1980
+ function parseLookupTableSegmented(content) {
1981
+ const lines = content.trim().split("\n");
1982
+ const lut = [];
1983
+ for (const line of lines) {
1984
+ const [indexStr, criteria, weightStr, payoutStr] = line.split(",");
1985
+ const index = parseInt(indexStr.trim());
1986
+ const weight = parseInt(weightStr.trim());
1987
+ const payout = parseFloat(payoutStr.trim());
1988
+ lut.push([index, criteria, weight, payout]);
1989
+ }
1990
+ return lut;
1991
+ }
1945
1992
  function getTotalLutWeight(lut) {
1946
1993
  return lut.reduce((sum, [, weight]) => sum + weight, 0);
1947
1994
  }
@@ -2054,7 +2101,7 @@ var Analysis = class {
2054
2101
  console.log("Analysis complete. Files written to build directory.");
2055
2102
  }
2056
2103
  getPathsForModes(gameModes) {
2057
- const rootPath = process.cwd();
2104
+ const rootPath = this.gameConfig.rootDir;
2058
2105
  const paths = {};
2059
2106
  for (const modeStr of gameModes) {
2060
2107
  const lut = import_path2.default.join(
@@ -2126,7 +2173,7 @@ var Analysis = class {
2126
2173
  });
2127
2174
  }
2128
2175
  writeJsonFile(
2129
- import_path2.default.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2176
+ import_path2.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "stats_summary.json"),
2130
2177
  stats
2131
2178
  );
2132
2179
  }
@@ -2155,35 +2202,76 @@ var Analysis = class {
2155
2202
  ];
2156
2203
  const payoutRanges = {};
2157
2204
  for (const modeStr of gameModes) {
2158
- payoutRanges[modeStr] = {};
2205
+ payoutRanges[modeStr] = { overall: {}, criteria: {} };
2159
2206
  const lutOptimized = parseLookupTable(
2160
2207
  import_fs3.default.readFileSync(this.filePaths[modeStr].lutOptimized, "utf-8")
2161
2208
  );
2209
+ const lutSegmented = parseLookupTableSegmented(
2210
+ import_fs3.default.readFileSync(this.filePaths[modeStr].lutSegmented, "utf-8")
2211
+ );
2162
2212
  lutOptimized.forEach(([, , p]) => {
2163
2213
  const payout = p / 100;
2164
2214
  for (const [min, max] of winRanges) {
2165
2215
  if (payout >= min && payout <= max) {
2166
2216
  const rangeKey = `${min}-${max}`;
2167
- if (!payoutRanges[modeStr][rangeKey]) {
2168
- payoutRanges[modeStr][rangeKey] = 0;
2217
+ if (!payoutRanges[modeStr].overall[rangeKey]) {
2218
+ payoutRanges[modeStr].overall[rangeKey] = 0;
2219
+ }
2220
+ payoutRanges[modeStr].overall[rangeKey] += 1;
2221
+ break;
2222
+ }
2223
+ }
2224
+ });
2225
+ lutSegmented.forEach(([, criteria, bp, fsp]) => {
2226
+ const basePayout = bp / 100;
2227
+ const freeSpinPayout = fsp / 100;
2228
+ const payout = basePayout + freeSpinPayout;
2229
+ for (const [min, max] of winRanges) {
2230
+ if (payout >= min && payout <= max) {
2231
+ const rangeKey = `${min}-${max}`;
2232
+ if (!payoutRanges[modeStr].overall[rangeKey]) {
2233
+ payoutRanges[modeStr].overall[rangeKey] = 0;
2234
+ }
2235
+ payoutRanges[modeStr].overall[rangeKey] += 1;
2236
+ if (!payoutRanges[modeStr].criteria[criteria]) {
2237
+ payoutRanges[modeStr].criteria[criteria] = {};
2169
2238
  }
2170
- payoutRanges[modeStr][rangeKey] += 1;
2239
+ if (!payoutRanges[modeStr].criteria[criteria][rangeKey]) {
2240
+ payoutRanges[modeStr].criteria[criteria][rangeKey] = 0;
2241
+ }
2242
+ payoutRanges[modeStr].criteria[criteria][rangeKey] += 1;
2171
2243
  break;
2172
2244
  }
2173
2245
  }
2174
2246
  });
2175
- const orderedRanges = {};
2176
- Object.keys(payoutRanges[modeStr]).sort((a, b) => {
2247
+ const orderedOverall = {};
2248
+ Object.keys(payoutRanges[modeStr].overall).sort((a, b) => {
2177
2249
  const [aMin] = a.split("-").map(Number);
2178
2250
  const [bMin] = b.split("-").map(Number);
2179
2251
  return aMin - bMin;
2180
2252
  }).forEach((key) => {
2181
- orderedRanges[key] = payoutRanges[modeStr][key];
2253
+ orderedOverall[key] = payoutRanges[modeStr].overall[key];
2254
+ });
2255
+ const orderedCriteria = {};
2256
+ Object.keys(payoutRanges[modeStr].criteria).forEach((crit) => {
2257
+ const critMap = payoutRanges[modeStr].criteria[crit];
2258
+ const orderedCritMap = {};
2259
+ Object.keys(critMap).sort((a, b) => {
2260
+ const [aMin] = a.split("-").map(Number);
2261
+ const [bMin] = b.split("-").map(Number);
2262
+ return aMin - bMin;
2263
+ }).forEach((key) => {
2264
+ orderedCritMap[key] = critMap[key];
2265
+ });
2266
+ orderedCriteria[crit] = orderedCritMap;
2182
2267
  });
2183
- payoutRanges[modeStr] = orderedRanges;
2268
+ payoutRanges[modeStr] = {
2269
+ overall: orderedOverall,
2270
+ criteria: {}
2271
+ };
2184
2272
  }
2185
2273
  writeJsonFile(
2186
- import_path2.default.join(process.cwd(), this.gameConfig.outputDir, "stats_payouts.json"),
2274
+ import_path2.default.join(this.gameConfig.rootDir, this.gameConfig.outputDir, "stats_payouts.json"),
2187
2275
  payoutRanges
2188
2276
  );
2189
2277
  }
@@ -2245,7 +2333,7 @@ function makeMathConfig(optimizer, opts = {}) {
2245
2333
  }))
2246
2334
  };
2247
2335
  if (writeToFile) {
2248
- const outPath = import_path3.default.join(process.cwd(), game.outputDir, "math_config.json");
2336
+ const outPath = import_path3.default.join(game.rootDir, game.outputDir, "math_config.json");
2249
2337
  writeJsonFile(outPath, config);
2250
2338
  }
2251
2339
  return config;
@@ -2288,7 +2376,7 @@ function makeSetupFile(optimizer, gameMode) {
2288
2376
  `;
2289
2377
  content += `simulation_trials;${params.simulationTrials}
2290
2378
  `;
2291
- content += `user_game_build_path;${import_path4.default.join(process.cwd(), gameConfig.outputDir)}
2379
+ content += `user_game_build_path;${import_path4.default.join(gameConfig.rootDir, gameConfig.outputDir)}
2292
2380
  `;
2293
2381
  content += `pmb_rtp;${params.pmbRtp}
2294
2382
  `;
@@ -2462,9 +2550,9 @@ var Optimizer = class {
2462
2550
  }
2463
2551
  };
2464
2552
  async function rustProgram(...args) {
2553
+ console.log("Starting Rust optimizer. This may take a while...");
2465
2554
  return new Promise((resolve, reject) => {
2466
- const task = (0, import_child_process.spawn)("cargo", ["run", "--release", ...args], {
2467
- shell: true,
2555
+ const task = (0, import_child_process.spawn)("cargo", ["run", "-q", "--release", ...args], {
2468
2556
  cwd: import_path5.default.join(__dirname, "./optimizer-rust"),
2469
2557
  stdio: "pipe"
2470
2558
  });
@@ -2635,9 +2723,9 @@ var WinType = class {
2635
2723
  * Custom post-processing of wins, e.g. for handling multipliers.
2636
2724
  */
2637
2725
  postProcess(func) {
2638
- const result = func(this, this.ctx);
2639
- this.payout = result.payout;
2726
+ const result = func(this.winCombinations, this.ctx);
2640
2727
  this.winCombinations = result.winCombinations;
2728
+ this.payout = result.winCombinations.reduce((sum, w) => sum + w.payout, 0);
2641
2729
  return this;
2642
2730
  }
2643
2731
  /**
@@ -2930,8 +3018,6 @@ var ClusterWinType = class extends WinType {
2930
3018
 
2931
3019
  // src/win-types/ManywaysWinType.ts
2932
3020
  var ManywaysWinType = class extends WinType {
2933
- _checked = [];
2934
- _checkedWilds = [];
2935
3021
  constructor(opts) {
2936
3022
  super(opts);
2937
3023
  }
@@ -3029,12 +3115,6 @@ var ManywaysWinType = class extends WinType {
3029
3115
  getWayLength(symbolList) {
3030
3116
  return Math.max(...Object.keys(symbolList).map((k) => parseInt(k, 10))) + 1;
3031
3117
  }
3032
- isChecked(ridx, sidx) {
3033
- return !!this._checked.find((c) => c.reel === ridx && c.row === sidx);
3034
- }
3035
- isCheckedWild(ridx, sidx) {
3036
- return !!this._checkedWilds.find((c) => c.reel === ridx && c.row === sidx);
3037
- }
3038
3118
  };
3039
3119
 
3040
3120
  // src/reel-set/GeneratedReelSet.ts
@@ -3238,6 +3318,7 @@ var GeneratedReelSet = class extends ReelSet {
3238
3318
  );
3239
3319
  }
3240
3320
  const filePath = import_path7.default.join(
3321
+ config.rootDir,
3241
3322
  config.outputDir,
3242
3323
  `reels_${this.associatedGameModeName}-${this.id}.csv`
3243
3324
  );