@slot-engine/core 0.0.2 → 0.0.4

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.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/GameConfig.ts
2
+ import assert from "assert";
2
3
  var GameConfig = class _GameConfig {
3
4
  config;
4
5
  constructor(opts) {
@@ -22,14 +23,12 @@ var GameConfig = class _GameConfig {
22
23
  userState: opts.userState,
23
24
  outputDir: "__build__"
24
25
  };
25
- for (const symbol of opts.symbols) {
26
- if (!this.config.symbols.has(symbol.id)) {
27
- this.config.symbols.set(symbol.id, symbol);
28
- } else {
29
- console.warn(
30
- `Symbol with id "${symbol.id}" already exists in the game config. Skipping duplicate. This is probably not intentional.`
31
- );
32
- }
26
+ for (const [key, value] of Object.entries(opts.symbols)) {
27
+ assert(
28
+ value.id === key,
29
+ `Symbol key "${key}" does not match symbol id "${value.id}"`
30
+ );
31
+ this.config.symbols.set(key, value);
33
32
  }
34
33
  function getAnticipationTrigger(spinType) {
35
34
  return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
@@ -63,7 +62,7 @@ var GameConfig = class _GameConfig {
63
62
  `Reel set with id "${id}" not found in game mode "${gameMode}". Available reel sets: ${this.config.gameModes[gameMode].reelSets.map((rs) => rs.id).join(", ")}`
64
63
  );
65
64
  }
66
- return reelSet;
65
+ return reelSet.reels;
67
66
  }
68
67
  /**
69
68
  * Retrieves the number of free spins awarded for a given spin type and scatter count.
@@ -80,7 +79,7 @@ var GameConfig = class _GameConfig {
80
79
  /**
81
80
  * Retrieves a result set by its criteria within a specific game mode.
82
81
  */
83
- getGameModeCriteria(mode, criteria) {
82
+ getResultSetByCriteria(mode, criteria) {
84
83
  const gameMode = this.config.gameModes[mode];
85
84
  if (!gameMode) {
86
85
  throw new Error(`Game mode "${mode}" not found in game config.`);
@@ -1050,13 +1049,6 @@ var GameState = class extends GameConfig {
1050
1049
  this.clearPendingRecords();
1051
1050
  this.state.userData = this.config.userState || {};
1052
1051
  }
1053
- /**
1054
- * Checks if a max win is reached by comparing `wallet.currentWin` to `config.maxWin`.
1055
- *
1056
- * Should be called after `wallet.confirmSpinWin()`.
1057
- */
1058
- isMaxWinTriggered() {
1059
- }
1060
1052
  /**
1061
1053
  * Empties the list of pending records in the recorder.
1062
1054
  */
@@ -1502,7 +1494,7 @@ var Board = class extends GameState {
1502
1494
  return stopPositionsForReels;
1503
1495
  }
1504
1496
  /**
1505
- * Selects a random reelset based on the configured weights for the current game mode.\
1497
+ * Selects a random reel set based on the configured weights of the current result set.\
1506
1498
  * Returns the reels as arrays of GameSymbols.
1507
1499
  */
1508
1500
  getRandomReelset() {
@@ -1517,7 +1509,7 @@ var Board = class extends GameState {
1517
1509
  reelSetId = weightedRandom(weights[this.state.currentSpinType], this.state.rng);
1518
1510
  }
1519
1511
  const reelSet = this.getReelsetById(this.state.currentGameMode, reelSetId);
1520
- return reelSet.reels;
1512
+ return reelSet;
1521
1513
  }
1522
1514
  /**
1523
1515
  * Draws a board using specified reel stops.
@@ -1773,7 +1765,7 @@ var ManywaysWinType = class extends WinType {
1773
1765
  };
1774
1766
 
1775
1767
  // src/optimizer/OptimizationConditions.ts
1776
- import assert from "assert";
1768
+ import assert2 from "assert";
1777
1769
  var OptimizationConditions = class {
1778
1770
  rtp;
1779
1771
  avgWin;
@@ -1784,14 +1776,14 @@ var OptimizationConditions = class {
1784
1776
  constructor(opts) {
1785
1777
  let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
1786
1778
  if (rtp == void 0 || rtp === "x") {
1787
- assert(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
1779
+ assert2(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
1788
1780
  rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
1789
1781
  }
1790
1782
  let noneCount = 0;
1791
1783
  for (const val of [rtp, avgWin, hitRate]) {
1792
1784
  if (val === void 0) noneCount++;
1793
1785
  }
1794
- assert(noneCount <= 1, "Invalid combination of optimization conditions.");
1786
+ assert2(noneCount <= 1, "Invalid combination of optimization conditions.");
1795
1787
  this.searchRange = [-1, -1];
1796
1788
  this.forceSearch = {};
1797
1789
  if (typeof searchConditions === "number") {
@@ -1871,41 +1863,11 @@ var OptimizationParameters = class _OptimizationParameters {
1871
1863
 
1872
1864
  // src/Simulation.ts
1873
1865
  import fs3 from "fs";
1874
- import path3 from "path";
1875
- import assert2 from "assert";
1866
+ import path2 from "path";
1867
+ import assert3 from "assert";
1868
+ import zlib from "zlib";
1876
1869
  import { buildSync } from "esbuild";
1877
1870
  import { Worker, isMainThread as isMainThread2, parentPort, workerData } from "worker_threads";
1878
-
1879
- // src/utils/zstd.ts
1880
- import path2 from "path";
1881
- import { spawn } from "child_process";
1882
- async function zstd(...args) {
1883
- return new Promise((resolve, reject) => {
1884
- const task = spawn(path2.join(__dirname, "./lib/zstd.exe"), args);
1885
- task.on("error", (error) => {
1886
- console.error("Error:", error);
1887
- reject(error);
1888
- });
1889
- task.on("exit", () => {
1890
- resolve(true);
1891
- });
1892
- task.on("close", () => {
1893
- resolve(true);
1894
- });
1895
- task.stdout.on("data", (data) => {
1896
- console.log(data.toString());
1897
- });
1898
- task.stderr.on("data", (data) => {
1899
- console.log(data.toString());
1900
- });
1901
- task.stdout.on("error", (data) => {
1902
- console.log(data.toString());
1903
- reject(data.toString());
1904
- });
1905
- });
1906
- }
1907
-
1908
- // src/Simulation.ts
1909
1871
  var completedSimulations = 0;
1910
1872
  var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
1911
1873
  var Simulation = class _Simulation {
@@ -1926,7 +1888,7 @@ var Simulation = class _Simulation {
1926
1888
  this.library = /* @__PURE__ */ new Map();
1927
1889
  this.records = [];
1928
1890
  const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
1929
- assert2(
1891
+ assert3(
1930
1892
  Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
1931
1893
  "Game mode name must match its key in the gameModes object."
1932
1894
  );
@@ -1963,14 +1925,14 @@ Simulating game mode: ${mode}`);
1963
1925
  const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
1964
1926
  await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
1965
1927
  createDirIfNotExists(
1966
- path3.join(
1928
+ path2.join(
1967
1929
  process.cwd(),
1968
1930
  this.gameConfig.config.outputDir,
1969
1931
  "optimization_files"
1970
1932
  )
1971
1933
  );
1972
1934
  createDirIfNotExists(
1973
- path3.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
1935
+ path2.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
1974
1936
  );
1975
1937
  _Simulation.writeLookupTableCSV({
1976
1938
  gameMode: mode,
@@ -2066,7 +2028,7 @@ Simulating game mode: ${mode}`);
2066
2028
  }
2067
2029
  }
2068
2030
  return new Promise((resolve, reject) => {
2069
- const scriptPath = path3.join(process.cwd(), basePath, TEMP_FILENAME);
2031
+ const scriptPath = path2.join(process.cwd(), basePath, TEMP_FILENAME);
2070
2032
  const worker = new Worker(scriptPath, {
2071
2033
  workerData: {
2072
2034
  mode,
@@ -2114,10 +2076,10 @@ Simulating game mode: ${mode}`);
2114
2076
  }
2115
2077
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
2116
2078
  let outputFileName = `lookUpTable_${gameMode}.csv`;
2117
- let outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2079
+ let outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2118
2080
  writeFile(outputFilePath, rows.join("\n"));
2119
2081
  outputFileName = `lookUpTable_${gameMode}_0.csv`;
2120
- outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2082
+ outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2121
2083
  writeFile(outputFilePath, rows.join("\n"));
2122
2084
  return outputFilePath;
2123
2085
  }
@@ -2134,21 +2096,21 @@ Simulating game mode: ${mode}`);
2134
2096
  }
2135
2097
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
2136
2098
  const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
2137
- const outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2099
+ const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2138
2100
  writeFile(outputFilePath, rows.join("\n"));
2139
2101
  return outputFilePath;
2140
2102
  }
2141
2103
  static writeRecords(opts) {
2142
2104
  const { gameMode, fileNameWithoutExtension, records, gameConfig, debug } = opts;
2143
2105
  const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.json` : `force_record_${gameMode}.json`;
2144
- const outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2106
+ const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2145
2107
  writeFile(outputFilePath, JSON.stringify(records, null, 2));
2146
2108
  if (debug) _Simulation.logSymbolOccurrences(records);
2147
2109
  return outputFilePath;
2148
2110
  }
2149
2111
  static writeIndexJson(opts) {
2150
2112
  const { gameConfig } = opts;
2151
- const outputFilePath = path3.join(
2113
+ const outputFilePath = path2.join(
2152
2114
  process.cwd(),
2153
2115
  gameConfig.outputDir,
2154
2116
  "publish_files",
@@ -2165,7 +2127,7 @@ Simulating game mode: ${mode}`);
2165
2127
  static async writeBooksJson(opts) {
2166
2128
  const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
2167
2129
  const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl` : `books_${gameMode}.jsonl`;
2168
- const outputFilePath = path3.join(gameConfig.outputDir, outputFileName);
2130
+ const outputFilePath = path2.join(gameConfig.outputDir, outputFileName);
2169
2131
  const books = Array.from(library.values()).map((b) => b.serialize()).map((b) => ({
2170
2132
  id: b.id,
2171
2133
  payoutMultiplier: b.payout,
@@ -2174,14 +2136,15 @@ Simulating game mode: ${mode}`);
2174
2136
  const contents = JSONL.stringify(books);
2175
2137
  writeFile(outputFilePath, contents);
2176
2138
  const compressedFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl.zst` : `books_${gameMode}.jsonl.zst`;
2177
- const compressedFilePath = path3.join(
2139
+ const compressedFilePath = path2.join(
2178
2140
  process.cwd(),
2179
2141
  gameConfig.outputDir,
2180
2142
  "publish_files",
2181
2143
  compressedFileName
2182
2144
  );
2183
2145
  fs3.rmSync(compressedFilePath, { force: true });
2184
- await zstd("-f", outputFilePath, "-o", compressedFilePath);
2146
+ const compressed = zlib.zstdCompressSync(Buffer.from(contents));
2147
+ fs3.writeFileSync(compressedFilePath, compressed);
2185
2148
  }
2186
2149
  static logSymbolOccurrences(records) {
2187
2150
  const validRecords = records.filter(
@@ -2212,14 +2175,14 @@ Simulating game mode: ${mode}`);
2212
2175
  * Compiles user configured game to JS for use in different Node processes
2213
2176
  */
2214
2177
  preprocessFiles() {
2215
- const builtFilePath = path3.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
2178
+ const builtFilePath = path2.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
2216
2179
  fs3.rmSync(builtFilePath, { force: true });
2217
2180
  buildSync({
2218
2181
  entryPoints: [process.cwd()],
2219
2182
  bundle: true,
2220
2183
  platform: "node",
2221
- outfile: path3.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
2222
- external: ["esbuild", "@mongodb-js/zstd"]
2184
+ outfile: path2.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
2185
+ external: ["esbuild"]
2223
2186
  });
2224
2187
  }
2225
2188
  getSimRangesForChunks(total, chunks) {
@@ -2276,22 +2239,25 @@ var SimulationContext = class extends Board {
2276
2239
  this.state.currentGameMode = mode;
2277
2240
  this.state.currentSimulationId = simId;
2278
2241
  this.state.isCriteriaMet = false;
2242
+ const resultSet = this.getResultSetByCriteria(this.state.currentGameMode, criteria);
2279
2243
  while (!this.state.isCriteriaMet) {
2280
2244
  this.actualSims++;
2281
2245
  this.resetSimulation();
2282
- const resultSet = this.getGameModeCriteria(this.state.currentGameMode, criteria);
2283
2246
  this.state.currentResultSet = resultSet;
2284
2247
  this.state.book.criteria = resultSet.criteria;
2285
2248
  this.handleGameFlow();
2286
2249
  if (resultSet.meetsCriteria(this)) {
2287
2250
  this.state.isCriteriaMet = true;
2288
- this.config.hooks.onSimulationAccepted?.(this);
2289
- this.record({
2290
- criteria: resultSet.criteria
2291
- });
2292
2251
  }
2293
2252
  }
2294
2253
  this.wallet.confirmWins(this);
2254
+ if (this.state.book.getPayout() >= this.config.maxWinX) {
2255
+ this.state.triggeredMaxWin = true;
2256
+ }
2257
+ this.record({
2258
+ criteria: resultSet.criteria
2259
+ });
2260
+ this.config.hooks.onSimulationAccepted?.(this);
2295
2261
  this.confirmRecords();
2296
2262
  parentPort?.postMessage({
2297
2263
  type: "complete",
@@ -2327,8 +2293,8 @@ var SimulationContext = class extends Board {
2327
2293
 
2328
2294
  // src/analysis/index.ts
2329
2295
  import fs4 from "fs";
2330
- import path4 from "path";
2331
- import assert3 from "assert";
2296
+ import path3 from "path";
2297
+ import assert4 from "assert";
2332
2298
 
2333
2299
  // src/analysis/utils.ts
2334
2300
  function parseLookupTable(content) {
@@ -2372,8 +2338,11 @@ function getPayoutWeights(lut, opts = {}) {
2372
2338
  }
2373
2339
  function getNonZeroHitrate(payoutWeights) {
2374
2340
  const totalWeight = getTotalWeight(payoutWeights);
2375
- const nonZeroWeight = totalWeight - (payoutWeights[0] ?? 0) * totalWeight;
2376
- return nonZeroWeight / totalWeight;
2341
+ if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {
2342
+ return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight);
2343
+ } else {
2344
+ return 1;
2345
+ }
2377
2346
  }
2378
2347
  function getNullHitrate(payoutWeights) {
2379
2348
  return payoutWeights[0] ?? 0;
@@ -2434,6 +2403,7 @@ function getLessBetHitrate(payoutWeights, cost) {
2434
2403
  }
2435
2404
 
2436
2405
  // src/analysis/index.ts
2406
+ import { isMainThread as isMainThread3 } from "worker_threads";
2437
2407
  var Analysis = class {
2438
2408
  gameConfig;
2439
2409
  optimizerConfig;
@@ -2444,6 +2414,7 @@ var Analysis = class {
2444
2414
  this.filePaths = {};
2445
2415
  }
2446
2416
  async runAnalysis(gameModes) {
2417
+ if (!isMainThread3) return;
2447
2418
  this.filePaths = this.getPathsForModes(gameModes);
2448
2419
  this.getNumberStats(gameModes);
2449
2420
  this.getWinRanges(gameModes);
@@ -2453,28 +2424,28 @@ var Analysis = class {
2453
2424
  const rootPath = process.cwd();
2454
2425
  const paths = {};
2455
2426
  for (const modeStr of gameModes) {
2456
- const lut = path4.join(
2427
+ const lut = path3.join(
2457
2428
  rootPath,
2458
2429
  this.gameConfig.outputDir,
2459
2430
  `lookUpTable_${modeStr}.csv`
2460
2431
  );
2461
- const lutSegmented = path4.join(
2432
+ const lutSegmented = path3.join(
2462
2433
  rootPath,
2463
2434
  this.gameConfig.outputDir,
2464
2435
  `lookUpTableSegmented_${modeStr}.csv`
2465
2436
  );
2466
- const lutOptimized = path4.join(
2437
+ const lutOptimized = path3.join(
2467
2438
  rootPath,
2468
2439
  this.gameConfig.outputDir,
2469
2440
  "publish_files",
2470
2441
  `lookUpTable_${modeStr}_0.csv`
2471
2442
  );
2472
- const booksJsonl = path4.join(
2443
+ const booksJsonl = path3.join(
2473
2444
  rootPath,
2474
2445
  this.gameConfig.outputDir,
2475
2446
  `books_${modeStr}.jsonl`
2476
2447
  );
2477
- const booksJsonlCompressed = path4.join(
2448
+ const booksJsonlCompressed = path3.join(
2478
2449
  rootPath,
2479
2450
  this.gameConfig.outputDir,
2480
2451
  "publish_files",
@@ -2488,7 +2459,7 @@ var Analysis = class {
2488
2459
  booksJsonlCompressed
2489
2460
  };
2490
2461
  for (const p of Object.values(paths[modeStr])) {
2491
- assert3(
2462
+ assert4(
2492
2463
  fs4.existsSync(p),
2493
2464
  `File "${p}" does not exist. Run optimization to auto-create it.`
2494
2465
  );
@@ -2522,7 +2493,7 @@ var Analysis = class {
2522
2493
  });
2523
2494
  }
2524
2495
  writeJsonFile(
2525
- path4.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2496
+ path3.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2526
2497
  stats
2527
2498
  );
2528
2499
  }
@@ -2560,13 +2531,13 @@ var Analysis = class {
2560
2531
  }
2561
2532
  getGameModeConfig(mode) {
2562
2533
  const config = this.gameConfig.gameModes[mode];
2563
- assert3(config, `Game mode "${mode}" not found in game config`);
2534
+ assert4(config, `Game mode "${mode}" not found in game config`);
2564
2535
  return config;
2565
2536
  }
2566
2537
  };
2567
2538
 
2568
2539
  // src/utils/math-config.ts
2569
- import path5 from "path";
2540
+ import path4 from "path";
2570
2541
  function makeMathConfig(optimizer, opts = {}) {
2571
2542
  const game = optimizer.getGameConfig();
2572
2543
  const gameModesCfg = optimizer.getOptimizerGameModes();
@@ -2610,14 +2581,14 @@ function makeMathConfig(optimizer, opts = {}) {
2610
2581
  }))
2611
2582
  };
2612
2583
  if (writeToFile) {
2613
- const outPath = path5.join(process.cwd(), game.outputDir, "math_config.json");
2584
+ const outPath = path4.join(process.cwd(), game.outputDir, "math_config.json");
2614
2585
  writeJsonFile(outPath, config);
2615
2586
  }
2616
2587
  return config;
2617
2588
  }
2618
2589
 
2619
2590
  // src/utils/setup-file.ts
2620
- import path6 from "path";
2591
+ import path5 from "path";
2621
2592
  function makeSetupFile(optimizer, gameMode) {
2622
2593
  const gameConfig = optimizer.getGameConfig();
2623
2594
  const optimizerGameModes = optimizer.getOptimizerGameModes();
@@ -2653,19 +2624,19 @@ function makeSetupFile(optimizer, gameMode) {
2653
2624
  `;
2654
2625
  content += `simulation_trials;${params.simulationTrials}
2655
2626
  `;
2656
- content += `user_game_build_path;${path6.join(process.cwd(), gameConfig.outputDir)}
2627
+ content += `user_game_build_path;${path5.join(process.cwd(), gameConfig.outputDir)}
2657
2628
  `;
2658
2629
  content += `pmb_rtp;${params.pmbRtp}
2659
2630
  `;
2660
- const outPath = path6.join(__dirname, "./optimizer-rust/src", "setup.txt");
2631
+ const outPath = path5.join(__dirname, "./optimizer-rust/src", "setup.txt");
2661
2632
  writeFile(outPath, content);
2662
2633
  }
2663
2634
 
2664
2635
  // src/optimizer/index.ts
2665
- import { spawn as spawn2 } from "child_process";
2666
- import path7 from "path";
2667
- import assert4 from "assert";
2668
- import { isMainThread as isMainThread3 } from "worker_threads";
2636
+ import { spawn } from "child_process";
2637
+ import path6 from "path";
2638
+ import assert5 from "assert";
2639
+ import { isMainThread as isMainThread4 } from "worker_threads";
2669
2640
  var Optimizer = class {
2670
2641
  gameConfig;
2671
2642
  gameModes;
@@ -2678,12 +2649,13 @@ var Optimizer = class {
2678
2649
  * Runs the optimization process, and runs analysis after.
2679
2650
  */
2680
2651
  async runOptimization({ gameModes }) {
2681
- if (!isMainThread3) return;
2652
+ if (!isMainThread4) return;
2682
2653
  const mathConfig = makeMathConfig(this, { writeToFile: true });
2683
2654
  for (const mode of gameModes) {
2684
2655
  const setupFile = makeSetupFile(this, mode);
2685
2656
  await this.runSingleOptimization();
2686
2657
  }
2658
+ console.log("Optimization complete. Files written to build directory.");
2687
2659
  }
2688
2660
  async runSingleOptimization() {
2689
2661
  return await rustProgram();
@@ -2707,7 +2679,7 @@ var Optimizer = class {
2707
2679
  }
2708
2680
  }
2709
2681
  const criteria = configMode.resultSets.map((r) => r.criteria);
2710
- assert4(
2682
+ assert5(
2711
2683
  conditions.every((c) => criteria.includes(c)),
2712
2684
  `Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
2713
2685
  );
@@ -2719,7 +2691,7 @@ var Optimizer = class {
2719
2691
  }
2720
2692
  gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
2721
2693
  paramRtp = Math.round(paramRtp * 1e3) / 1e3;
2722
- assert4(
2694
+ assert5(
2723
2695
  gameModeRtp === paramRtp,
2724
2696
  `Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
2725
2697
  );
@@ -2734,9 +2706,9 @@ var Optimizer = class {
2734
2706
  };
2735
2707
  async function rustProgram(...args) {
2736
2708
  return new Promise((resolve, reject) => {
2737
- const task = spawn2("cargo", ["run", "--release", ...args], {
2709
+ const task = spawn("cargo", ["run", "--release", ...args], {
2738
2710
  shell: true,
2739
- cwd: path7.join(__dirname, "./optimizer-rust"),
2711
+ cwd: path6.join(__dirname, "./optimizer-rust"),
2740
2712
  stdio: "pipe"
2741
2713
  });
2742
2714
  task.on("error", (error) => {