@slot-engine/core 0.0.1 → 0.0.3

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 (43) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.mts +7 -14
  3. package/dist/index.d.ts +7 -14
  4. package/dist/index.js +68 -90
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +68 -90
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +5 -1
  9. package/.turbo/turbo-build.log +0 -33
  10. package/.turbo/turbo-typecheck.log +0 -4
  11. package/CHANGELOG.md +0 -7
  12. package/dist/lib/zstd.exe +0 -0
  13. package/index.ts +0 -205
  14. package/lib/zstd.exe +0 -0
  15. package/optimizer-rust/Cargo.toml +0 -19
  16. package/optimizer-rust/src/exes.rs +0 -154
  17. package/optimizer-rust/src/main.rs +0 -1659
  18. package/src/Board.ts +0 -527
  19. package/src/Book.ts +0 -83
  20. package/src/GameConfig.ts +0 -148
  21. package/src/GameMode.ts +0 -86
  22. package/src/GameState.ts +0 -272
  23. package/src/GameSymbol.ts +0 -61
  24. package/src/ReelGenerator.ts +0 -589
  25. package/src/ResultSet.ts +0 -207
  26. package/src/Simulation.ts +0 -625
  27. package/src/SlotGame.ts +0 -117
  28. package/src/Wallet.ts +0 -203
  29. package/src/WinType.ts +0 -102
  30. package/src/analysis/index.ts +0 -198
  31. package/src/analysis/utils.ts +0 -128
  32. package/src/optimizer/OptimizationConditions.ts +0 -99
  33. package/src/optimizer/OptimizationParameters.ts +0 -46
  34. package/src/optimizer/OptimizationScaling.ts +0 -18
  35. package/src/optimizer/index.ts +0 -142
  36. package/src/utils/math-config.ts +0 -109
  37. package/src/utils/setup-file.ts +0 -36
  38. package/src/utils/zstd.ts +0 -28
  39. package/src/winTypes/ClusterWinType.ts +0 -3
  40. package/src/winTypes/LinesWinType.ts +0 -208
  41. package/src/winTypes/ManywaysWinType.ts +0 -3
  42. package/tsconfig.json +0 -19
  43. package/utils.ts +0 -270
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!NOTE]
2
- > This software **is work in progress** and may currently miss features or contain bugs. Feel free to contribute to help improve this project. Breaking changes may occur at any time during beta.
2
+ > This software **is work in progress** and may currently miss features or contain bugs. Feel free to contribute to help improve this project. Breaking changes may occur at any time.
3
3
 
4
4
  # Slot Engine Core
5
5
 
package/dist/index.d.mts CHANGED
@@ -878,9 +878,8 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
878
878
  readonly config: {
879
879
  readonly id: string;
880
880
  readonly name: string;
881
- readonly providerNumber: number;
882
881
  readonly gameModes: Record<GameModeName, GameMode>;
883
- readonly symbols: Map<TSymbols[number]["id"], TSymbols[number]>;
882
+ readonly symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>;
884
883
  readonly padSymbols?: number;
885
884
  readonly scatterToFreespins: Record<string, Record<number, number>>;
886
885
  readonly anticipationTriggers: Record<SpinType, number>;
@@ -909,7 +908,7 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
909
908
  /**
910
909
  * Returns all configured symbols as an array.
911
910
  */
912
- getSymbolArray(): TSymbols[number][];
911
+ getSymbolArray(): TSymbols[keyof TSymbols][];
913
912
  static SPIN_TYPE: {
914
913
  readonly BASE_GAME: "basegame";
915
914
  readonly FREE_SPINS: "freespins";
@@ -929,7 +928,7 @@ declare class WinType {
929
928
  *
930
929
  * This gives the WinType access to the current board.
931
930
  */
932
- context(ctx: AnySimulationContext): WinType;
931
+ context(ctx: SimulationContext<any, any, any>): WinType;
933
932
  protected ensureContext(): void;
934
933
  /**
935
934
  * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
@@ -986,7 +985,7 @@ type WildSymbol = GameSymbol | Record<string, any>;
986
985
  declare class LinesWinType extends WinType {
987
986
  protected lines: Record<number, number[]>;
988
987
  protected winCombinations: LineWinCombination[];
989
- context: (ctx: AnySimulationContext) => LinesWinType;
988
+ context: (ctx: SimulationContext<any, any, any>) => LinesWinType;
990
989
  getWins: () => {
991
990
  payout: number;
992
991
  winCombinations: LineWinCombination[];
@@ -1159,7 +1158,6 @@ declare class SlotGame<TGameModes extends AnyGameModes = AnyGameModes, TSymbols
1159
1158
  */
1160
1159
  private runAnalysis;
1161
1160
  runTasks(opts?: {
1162
- debug?: boolean;
1163
1161
  doSimulation?: boolean;
1164
1162
  doOptimization?: boolean;
1165
1163
  doAnalysis?: boolean;
@@ -1185,10 +1183,6 @@ interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
1185
1183
  * The name of the game, used for display purposes.
1186
1184
  */
1187
1185
  name: string;
1188
- /**
1189
- * An arbitrary number to identify the provider of this game.
1190
- */
1191
- providerNumber: number;
1192
1186
  /**
1193
1187
  * A GameMode is the core structure of a slot, defining the board,\
1194
1188
  * bet cost, win type, and other properties.
@@ -1199,7 +1193,7 @@ interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
1199
1193
  /**
1200
1194
  * A list of all symbols that will appear on the reels.
1201
1195
  */
1202
- symbols: GameSymbol[];
1196
+ symbols: TSymbols;
1203
1197
  /**
1204
1198
  * A mapping from spin type to scatter counts to the number of free spins awarded.
1205
1199
  *
@@ -1252,7 +1246,7 @@ type AnyGameModes = Record<string, GameMode>;
1252
1246
  /**
1253
1247
  * @internal
1254
1248
  */
1255
- type AnySymbols = GameSymbol[];
1249
+ type AnySymbols = Record<string, GameSymbol>;
1256
1250
  /**
1257
1251
  * @internal
1258
1252
  */
@@ -1288,7 +1282,6 @@ type InferGameType<TGameModes extends AnyGameModes, TSymbols extends AnySymbols,
1288
1282
  interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
1289
1283
  id: CommonGameOptions["id"];
1290
1284
  name: CommonGameOptions["name"];
1291
- providerNumber: CommonGameOptions["providerNumber"];
1292
1285
  gameModes: TGameModes;
1293
1286
  symbols: TSymbols;
1294
1287
  scatterToFreespins: CommonGameOptions["scatterToFreespins"];
@@ -1299,7 +1292,7 @@ interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSy
1299
1292
  }
1300
1293
  declare function createSlotGame<TGame>(opts: TGame extends InferGameType<infer G, infer S, infer U> ? CreateSlotGameOpts<G, S, U> : never): TGame;
1301
1294
  declare const defineUserState: <TUserState extends AnyUserData>(data: TUserState) => TUserState;
1302
- declare const defineSymbols: <TSymbol extends GameSymbol>(symbols: TSymbol[]) => TSymbol[];
1295
+ declare const defineSymbols: <TSymbols extends AnySymbols>(symbols: TSymbols) => TSymbols;
1303
1296
  declare const defineGameModes: <TGameModes extends AnyGameModes>(gameModes: TGameModes) => TGameModes;
1304
1297
  declare const defineReelSets: <TSymbols extends AnySymbols>(reelSets: ReelGenerator[]) => ReelGenerator[];
1305
1298
 
package/dist/index.d.ts CHANGED
@@ -878,9 +878,8 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
878
878
  readonly config: {
879
879
  readonly id: string;
880
880
  readonly name: string;
881
- readonly providerNumber: number;
882
881
  readonly gameModes: Record<GameModeName, GameMode>;
883
- readonly symbols: Map<TSymbols[number]["id"], TSymbols[number]>;
882
+ readonly symbols: Map<keyof TSymbols & string, TSymbols[keyof TSymbols]>;
884
883
  readonly padSymbols?: number;
885
884
  readonly scatterToFreespins: Record<string, Record<number, number>>;
886
885
  readonly anticipationTriggers: Record<SpinType, number>;
@@ -909,7 +908,7 @@ declare class GameConfig<TGameModes extends AnyGameModes = AnyGameModes, TSymbol
909
908
  /**
910
909
  * Returns all configured symbols as an array.
911
910
  */
912
- getSymbolArray(): TSymbols[number][];
911
+ getSymbolArray(): TSymbols[keyof TSymbols][];
913
912
  static SPIN_TYPE: {
914
913
  readonly BASE_GAME: "basegame";
915
914
  readonly FREE_SPINS: "freespins";
@@ -929,7 +928,7 @@ declare class WinType {
929
928
  *
930
929
  * This gives the WinType access to the current board.
931
930
  */
932
- context(ctx: AnySimulationContext): WinType;
931
+ context(ctx: SimulationContext<any, any, any>): WinType;
933
932
  protected ensureContext(): void;
934
933
  /**
935
934
  * Implementation of win evaluation logic. Sets `this.payout` and `this.winCombinations`.
@@ -986,7 +985,7 @@ type WildSymbol = GameSymbol | Record<string, any>;
986
985
  declare class LinesWinType extends WinType {
987
986
  protected lines: Record<number, number[]>;
988
987
  protected winCombinations: LineWinCombination[];
989
- context: (ctx: AnySimulationContext) => LinesWinType;
988
+ context: (ctx: SimulationContext<any, any, any>) => LinesWinType;
990
989
  getWins: () => {
991
990
  payout: number;
992
991
  winCombinations: LineWinCombination[];
@@ -1159,7 +1158,6 @@ declare class SlotGame<TGameModes extends AnyGameModes = AnyGameModes, TSymbols
1159
1158
  */
1160
1159
  private runAnalysis;
1161
1160
  runTasks(opts?: {
1162
- debug?: boolean;
1163
1161
  doSimulation?: boolean;
1164
1162
  doOptimization?: boolean;
1165
1163
  doAnalysis?: boolean;
@@ -1185,10 +1183,6 @@ interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
1185
1183
  * The name of the game, used for display purposes.
1186
1184
  */
1187
1185
  name: string;
1188
- /**
1189
- * An arbitrary number to identify the provider of this game.
1190
- */
1191
- providerNumber: number;
1192
1186
  /**
1193
1187
  * A GameMode is the core structure of a slot, defining the board,\
1194
1188
  * bet cost, win type, and other properties.
@@ -1199,7 +1193,7 @@ interface CommonGameOptions<TGameModes extends AnyGameModes = AnyGameModes, TSym
1199
1193
  /**
1200
1194
  * A list of all symbols that will appear on the reels.
1201
1195
  */
1202
- symbols: GameSymbol[];
1196
+ symbols: TSymbols;
1203
1197
  /**
1204
1198
  * A mapping from spin type to scatter counts to the number of free spins awarded.
1205
1199
  *
@@ -1252,7 +1246,7 @@ type AnyGameModes = Record<string, GameMode>;
1252
1246
  /**
1253
1247
  * @internal
1254
1248
  */
1255
- type AnySymbols = GameSymbol[];
1249
+ type AnySymbols = Record<string, GameSymbol>;
1256
1250
  /**
1257
1251
  * @internal
1258
1252
  */
@@ -1288,7 +1282,6 @@ type InferGameType<TGameModes extends AnyGameModes, TSymbols extends AnySymbols,
1288
1282
  interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSymbols extends AnySymbols = AnySymbols, TUserState extends AnyUserData = AnyUserData> {
1289
1283
  id: CommonGameOptions["id"];
1290
1284
  name: CommonGameOptions["name"];
1291
- providerNumber: CommonGameOptions["providerNumber"];
1292
1285
  gameModes: TGameModes;
1293
1286
  symbols: TSymbols;
1294
1287
  scatterToFreespins: CommonGameOptions["scatterToFreespins"];
@@ -1299,7 +1292,7 @@ interface CreateSlotGameOpts<TGameModes extends AnyGameModes = AnyGameModes, TSy
1299
1292
  }
1300
1293
  declare function createSlotGame<TGame>(opts: TGame extends InferGameType<infer G, infer S, infer U> ? CreateSlotGameOpts<G, S, U> : never): TGame;
1301
1294
  declare const defineUserState: <TUserState extends AnyUserData>(data: TUserState) => TUserState;
1302
- declare const defineSymbols: <TSymbol extends GameSymbol>(symbols: TSymbol[]) => TSymbol[];
1295
+ declare const defineSymbols: <TSymbols extends AnySymbols>(symbols: TSymbols) => TSymbols;
1303
1296
  declare const defineGameModes: <TGameModes extends AnyGameModes>(gameModes: TGameModes) => TGameModes;
1304
1297
  declare const defineReelSets: <TSymbols extends AnySymbols>(reelSets: ReelGenerator[]) => ReelGenerator[];
1305
1298
 
package/dist/index.js CHANGED
@@ -53,13 +53,13 @@ __export(index_exports, {
53
53
  module.exports = __toCommonJS(index_exports);
54
54
 
55
55
  // src/GameConfig.ts
56
+ var import_assert = __toESM(require("assert"));
56
57
  var GameConfig = class _GameConfig {
57
58
  config;
58
59
  constructor(opts) {
59
60
  this.config = {
60
61
  id: opts.id,
61
62
  name: opts.name,
62
- providerNumber: opts.providerNumber,
63
63
  gameModes: opts.gameModes,
64
64
  symbols: /* @__PURE__ */ new Map(),
65
65
  padSymbols: opts.padSymbols || 0,
@@ -77,14 +77,12 @@ var GameConfig = class _GameConfig {
77
77
  userState: opts.userState,
78
78
  outputDir: "__build__"
79
79
  };
80
- for (const symbol of opts.symbols) {
81
- if (!this.config.symbols.has(symbol.id)) {
82
- this.config.symbols.set(symbol.id, symbol);
83
- } else {
84
- console.warn(
85
- `Symbol with id "${symbol.id}" already exists in the game config. Skipping duplicate. This is probably not intentional.`
86
- );
87
- }
80
+ for (const [key, value] of Object.entries(opts.symbols)) {
81
+ (0, import_assert.default)(
82
+ value.id === key,
83
+ `Symbol key "${key}" does not match symbol id "${value.id}"`
84
+ );
85
+ this.config.symbols.set(key, value);
88
86
  }
89
87
  function getAnticipationTrigger(spinType) {
90
88
  return Math.min(...Object.keys(opts.scatterToFreespins[spinType]).map(Number)) - 1;
@@ -1828,7 +1826,7 @@ var ManywaysWinType = class extends WinType {
1828
1826
  };
1829
1827
 
1830
1828
  // src/optimizer/OptimizationConditions.ts
1831
- var import_assert = __toESM(require("assert"));
1829
+ var import_assert2 = __toESM(require("assert"));
1832
1830
  var OptimizationConditions = class {
1833
1831
  rtp;
1834
1832
  avgWin;
@@ -1839,14 +1837,14 @@ var OptimizationConditions = class {
1839
1837
  constructor(opts) {
1840
1838
  let { rtp, avgWin, hitRate, searchConditions, priority } = opts;
1841
1839
  if (rtp == void 0 || rtp === "x") {
1842
- (0, import_assert.default)(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
1840
+ (0, import_assert2.default)(avgWin !== void 0 && hitRate !== void 0, "If RTP is not specified, hit-rate (hr) and average win amount (av_win) must be given.");
1843
1841
  rtp = Math.round(avgWin / Number(hitRate) * 1e5) / 1e5;
1844
1842
  }
1845
1843
  let noneCount = 0;
1846
1844
  for (const val of [rtp, avgWin, hitRate]) {
1847
1845
  if (val === void 0) noneCount++;
1848
1846
  }
1849
- (0, import_assert.default)(noneCount <= 1, "Invalid combination of optimization conditions.");
1847
+ (0, import_assert2.default)(noneCount <= 1, "Invalid combination of optimization conditions.");
1850
1848
  this.searchRange = [-1, -1];
1851
1849
  this.forceSearch = {};
1852
1850
  if (typeof searchConditions === "number") {
@@ -1926,41 +1924,11 @@ var OptimizationParameters = class _OptimizationParameters {
1926
1924
 
1927
1925
  // src/Simulation.ts
1928
1926
  var import_fs3 = __toESM(require("fs"));
1929
- var import_path3 = __toESM(require("path"));
1930
- var import_assert2 = __toESM(require("assert"));
1927
+ var import_path2 = __toESM(require("path"));
1928
+ var import_assert3 = __toESM(require("assert"));
1929
+ var import_zlib = __toESM(require("zlib"));
1931
1930
  var import_esbuild = require("esbuild");
1932
1931
  var import_worker_threads2 = require("worker_threads");
1933
-
1934
- // src/utils/zstd.ts
1935
- var import_path2 = __toESM(require("path"));
1936
- var import_child_process = require("child_process");
1937
- async function zstd(...args) {
1938
- return new Promise((resolve, reject) => {
1939
- const task = (0, import_child_process.spawn)(import_path2.default.join(__dirname, "./lib/zstd.exe"), args);
1940
- task.on("error", (error) => {
1941
- console.error("Error:", error);
1942
- reject(error);
1943
- });
1944
- task.on("exit", () => {
1945
- resolve(true);
1946
- });
1947
- task.on("close", () => {
1948
- resolve(true);
1949
- });
1950
- task.stdout.on("data", (data) => {
1951
- console.log(data.toString());
1952
- });
1953
- task.stderr.on("data", (data) => {
1954
- console.log(data.toString());
1955
- });
1956
- task.stdout.on("error", (data) => {
1957
- console.log(data.toString());
1958
- reject(data.toString());
1959
- });
1960
- });
1961
- }
1962
-
1963
- // src/Simulation.ts
1964
1932
  var completedSimulations = 0;
1965
1933
  var TEMP_FILENAME = "__temp_compiled_src_IGNORE.js";
1966
1934
  var Simulation = class _Simulation {
@@ -1981,7 +1949,7 @@ var Simulation = class _Simulation {
1981
1949
  this.library = /* @__PURE__ */ new Map();
1982
1950
  this.records = [];
1983
1951
  const gameModeKeys = Object.keys(this.gameConfig.config.gameModes);
1984
- (0, import_assert2.default)(
1952
+ (0, import_assert3.default)(
1985
1953
  Object.values(this.gameConfig.config.gameModes).map((m) => gameModeKeys.includes(m.name)).every((v) => v === true),
1986
1954
  "Game mode name must match its key in the gameModes object."
1987
1955
  );
@@ -2018,14 +1986,14 @@ Simulating game mode: ${mode}`);
2018
1986
  const simNumsToCriteria = ResultSet.assignCriteriaToSimulations(this, mode);
2019
1987
  await this.spawnWorkersForGameMode({ mode, simNumsToCriteria });
2020
1988
  createDirIfNotExists(
2021
- import_path3.default.join(
1989
+ import_path2.default.join(
2022
1990
  process.cwd(),
2023
1991
  this.gameConfig.config.outputDir,
2024
1992
  "optimization_files"
2025
1993
  )
2026
1994
  );
2027
1995
  createDirIfNotExists(
2028
- import_path3.default.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
1996
+ import_path2.default.join(process.cwd(), this.gameConfig.config.outputDir, "publish_files")
2029
1997
  );
2030
1998
  _Simulation.writeLookupTableCSV({
2031
1999
  gameMode: mode,
@@ -2121,7 +2089,7 @@ Simulating game mode: ${mode}`);
2121
2089
  }
2122
2090
  }
2123
2091
  return new Promise((resolve, reject) => {
2124
- const scriptPath = import_path3.default.join(process.cwd(), basePath, TEMP_FILENAME);
2092
+ const scriptPath = import_path2.default.join(process.cwd(), basePath, TEMP_FILENAME);
2125
2093
  const worker = new import_worker_threads2.Worker(scriptPath, {
2126
2094
  workerData: {
2127
2095
  mode,
@@ -2162,14 +2130,17 @@ Simulating game mode: ${mode}`);
2162
2130
  * `weight` defaults to 1.
2163
2131
  */
2164
2132
  static writeLookupTableCSV(opts) {
2165
- const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
2133
+ const { gameMode, library, gameConfig } = opts;
2166
2134
  const rows = [];
2167
2135
  for (const [bookId, book] of library.entries()) {
2168
2136
  rows.push(`${book.id},1,${Math.round(book.getPayout())}`);
2169
2137
  }
2170
2138
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
2171
- const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.csv` : `lookUpTable_${gameMode}.csv`;
2172
- const outputFilePath = import_path3.default.join(gameConfig.outputDir, outputFileName);
2139
+ let outputFileName = `lookUpTable_${gameMode}.csv`;
2140
+ let outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
2141
+ writeFile(outputFilePath, rows.join("\n"));
2142
+ outputFileName = `lookUpTable_${gameMode}_0.csv`;
2143
+ outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
2173
2144
  writeFile(outputFilePath, rows.join("\n"));
2174
2145
  return outputFilePath;
2175
2146
  }
@@ -2177,7 +2148,7 @@ Simulating game mode: ${mode}`);
2177
2148
  * Creates a CSV file in the format "simulationId,criteria,payoutBase,payoutFreespins".
2178
2149
  */
2179
2150
  static writeLookupTableSegmentedCSV(opts) {
2180
- const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
2151
+ const { gameMode, library, gameConfig } = opts;
2181
2152
  const rows = [];
2182
2153
  for (const [bookId, book] of library.entries()) {
2183
2154
  rows.push(
@@ -2185,22 +2156,22 @@ Simulating game mode: ${mode}`);
2185
2156
  );
2186
2157
  }
2187
2158
  rows.sort((a, b) => Number(a.split(",")[0]) - Number(b.split(",")[0]));
2188
- const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.csv` : `lookUpTableSegmented_${gameMode}.csv`;
2189
- const outputFilePath = import_path3.default.join(gameConfig.outputDir, outputFileName);
2159
+ const outputFileName = `lookUpTableSegmented_${gameMode}.csv`;
2160
+ const outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
2190
2161
  writeFile(outputFilePath, rows.join("\n"));
2191
2162
  return outputFilePath;
2192
2163
  }
2193
2164
  static writeRecords(opts) {
2194
2165
  const { gameMode, fileNameWithoutExtension, records, gameConfig, debug } = opts;
2195
2166
  const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.json` : `force_record_${gameMode}.json`;
2196
- const outputFilePath = import_path3.default.join(gameConfig.outputDir, outputFileName);
2167
+ const outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
2197
2168
  writeFile(outputFilePath, JSON.stringify(records, null, 2));
2198
2169
  if (debug) _Simulation.logSymbolOccurrences(records);
2199
2170
  return outputFilePath;
2200
2171
  }
2201
2172
  static writeIndexJson(opts) {
2202
2173
  const { gameConfig } = opts;
2203
- const outputFilePath = import_path3.default.join(
2174
+ const outputFilePath = import_path2.default.join(
2204
2175
  process.cwd(),
2205
2176
  gameConfig.outputDir,
2206
2177
  "publish_files",
@@ -2217,7 +2188,7 @@ Simulating game mode: ${mode}`);
2217
2188
  static async writeBooksJson(opts) {
2218
2189
  const { gameMode, fileNameWithoutExtension, library, gameConfig } = opts;
2219
2190
  const outputFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl` : `books_${gameMode}.jsonl`;
2220
- const outputFilePath = import_path3.default.join(gameConfig.outputDir, outputFileName);
2191
+ const outputFilePath = import_path2.default.join(gameConfig.outputDir, outputFileName);
2221
2192
  const books = Array.from(library.values()).map((b) => b.serialize()).map((b) => ({
2222
2193
  id: b.id,
2223
2194
  payoutMultiplier: b.payout,
@@ -2226,14 +2197,15 @@ Simulating game mode: ${mode}`);
2226
2197
  const contents = JSONL.stringify(books);
2227
2198
  writeFile(outputFilePath, contents);
2228
2199
  const compressedFileName = fileNameWithoutExtension ? `${fileNameWithoutExtension}.jsonl.zst` : `books_${gameMode}.jsonl.zst`;
2229
- const compressedFilePath = import_path3.default.join(
2200
+ const compressedFilePath = import_path2.default.join(
2230
2201
  process.cwd(),
2231
2202
  gameConfig.outputDir,
2232
2203
  "publish_files",
2233
2204
  compressedFileName
2234
2205
  );
2235
2206
  import_fs3.default.rmSync(compressedFilePath, { force: true });
2236
- await zstd("-f", outputFilePath, "-o", compressedFilePath);
2207
+ const compressed = import_zlib.default.zstdCompressSync(Buffer.from(contents));
2208
+ import_fs3.default.writeFileSync(compressedFilePath, compressed);
2237
2209
  }
2238
2210
  static logSymbolOccurrences(records) {
2239
2211
  const validRecords = records.filter(
@@ -2264,14 +2236,14 @@ Simulating game mode: ${mode}`);
2264
2236
  * Compiles user configured game to JS for use in different Node processes
2265
2237
  */
2266
2238
  preprocessFiles() {
2267
- const builtFilePath = import_path3.default.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
2239
+ const builtFilePath = import_path2.default.join(this.gameConfig.config.outputDir, TEMP_FILENAME);
2268
2240
  import_fs3.default.rmSync(builtFilePath, { force: true });
2269
2241
  (0, import_esbuild.buildSync)({
2270
2242
  entryPoints: [process.cwd()],
2271
2243
  bundle: true,
2272
2244
  platform: "node",
2273
- outfile: import_path3.default.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
2274
- external: ["esbuild", "@mongodb-js/zstd"]
2245
+ outfile: import_path2.default.join(this.gameConfig.config.outputDir, TEMP_FILENAME),
2246
+ external: ["esbuild"]
2275
2247
  });
2276
2248
  }
2277
2249
  getSimRangesForChunks(total, chunks) {
@@ -2379,8 +2351,8 @@ var SimulationContext = class extends Board {
2379
2351
 
2380
2352
  // src/analysis/index.ts
2381
2353
  var import_fs4 = __toESM(require("fs"));
2382
- var import_path4 = __toESM(require("path"));
2383
- var import_assert3 = __toESM(require("assert"));
2354
+ var import_path3 = __toESM(require("path"));
2355
+ var import_assert4 = __toESM(require("assert"));
2384
2356
 
2385
2357
  // src/analysis/utils.ts
2386
2358
  function parseLookupTable(content) {
@@ -2424,8 +2396,11 @@ function getPayoutWeights(lut, opts = {}) {
2424
2396
  }
2425
2397
  function getNonZeroHitrate(payoutWeights) {
2426
2398
  const totalWeight = getTotalWeight(payoutWeights);
2427
- const nonZeroWeight = totalWeight - (payoutWeights[0] ?? 0) * totalWeight;
2428
- return nonZeroWeight / totalWeight;
2399
+ if (Math.min(...Object.keys(payoutWeights).map(Number)) == 0) {
2400
+ return totalWeight / (totalWeight - (payoutWeights[0] ?? 0) / totalWeight);
2401
+ } else {
2402
+ return 1;
2403
+ }
2429
2404
  }
2430
2405
  function getNullHitrate(payoutWeights) {
2431
2406
  return payoutWeights[0] ?? 0;
@@ -2486,6 +2461,7 @@ function getLessBetHitrate(payoutWeights, cost) {
2486
2461
  }
2487
2462
 
2488
2463
  // src/analysis/index.ts
2464
+ var import_worker_threads3 = require("worker_threads");
2489
2465
  var Analysis = class {
2490
2466
  gameConfig;
2491
2467
  optimizerConfig;
@@ -2496,6 +2472,7 @@ var Analysis = class {
2496
2472
  this.filePaths = {};
2497
2473
  }
2498
2474
  async runAnalysis(gameModes) {
2475
+ if (!import_worker_threads3.isMainThread) return;
2499
2476
  this.filePaths = this.getPathsForModes(gameModes);
2500
2477
  this.getNumberStats(gameModes);
2501
2478
  this.getWinRanges(gameModes);
@@ -2505,28 +2482,28 @@ var Analysis = class {
2505
2482
  const rootPath = process.cwd();
2506
2483
  const paths = {};
2507
2484
  for (const modeStr of gameModes) {
2508
- const lut = import_path4.default.join(
2485
+ const lut = import_path3.default.join(
2509
2486
  rootPath,
2510
2487
  this.gameConfig.outputDir,
2511
2488
  `lookUpTable_${modeStr}.csv`
2512
2489
  );
2513
- const lutSegmented = import_path4.default.join(
2490
+ const lutSegmented = import_path3.default.join(
2514
2491
  rootPath,
2515
2492
  this.gameConfig.outputDir,
2516
2493
  `lookUpTableSegmented_${modeStr}.csv`
2517
2494
  );
2518
- const lutOptimized = import_path4.default.join(
2495
+ const lutOptimized = import_path3.default.join(
2519
2496
  rootPath,
2520
2497
  this.gameConfig.outputDir,
2521
2498
  "publish_files",
2522
2499
  `lookUpTable_${modeStr}_0.csv`
2523
2500
  );
2524
- const booksJsonl = import_path4.default.join(
2501
+ const booksJsonl = import_path3.default.join(
2525
2502
  rootPath,
2526
2503
  this.gameConfig.outputDir,
2527
2504
  `books_${modeStr}.jsonl`
2528
2505
  );
2529
- const booksJsonlCompressed = import_path4.default.join(
2506
+ const booksJsonlCompressed = import_path3.default.join(
2530
2507
  rootPath,
2531
2508
  this.gameConfig.outputDir,
2532
2509
  "publish_files",
@@ -2540,7 +2517,7 @@ var Analysis = class {
2540
2517
  booksJsonlCompressed
2541
2518
  };
2542
2519
  for (const p of Object.values(paths[modeStr])) {
2543
- (0, import_assert3.default)(
2520
+ (0, import_assert4.default)(
2544
2521
  import_fs4.default.existsSync(p),
2545
2522
  `File "${p}" does not exist. Run optimization to auto-create it.`
2546
2523
  );
@@ -2574,7 +2551,7 @@ var Analysis = class {
2574
2551
  });
2575
2552
  }
2576
2553
  writeJsonFile(
2577
- import_path4.default.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2554
+ import_path3.default.join(process.cwd(), this.gameConfig.outputDir, "stats_summary.json"),
2578
2555
  stats
2579
2556
  );
2580
2557
  }
@@ -2612,13 +2589,13 @@ var Analysis = class {
2612
2589
  }
2613
2590
  getGameModeConfig(mode) {
2614
2591
  const config = this.gameConfig.gameModes[mode];
2615
- (0, import_assert3.default)(config, `Game mode "${mode}" not found in game config`);
2592
+ (0, import_assert4.default)(config, `Game mode "${mode}" not found in game config`);
2616
2593
  return config;
2617
2594
  }
2618
2595
  };
2619
2596
 
2620
2597
  // src/utils/math-config.ts
2621
- var import_path5 = __toESM(require("path"));
2598
+ var import_path4 = __toESM(require("path"));
2622
2599
  function makeMathConfig(optimizer, opts = {}) {
2623
2600
  const game = optimizer.getGameConfig();
2624
2601
  const gameModesCfg = optimizer.getOptimizerGameModes();
@@ -2662,14 +2639,14 @@ function makeMathConfig(optimizer, opts = {}) {
2662
2639
  }))
2663
2640
  };
2664
2641
  if (writeToFile) {
2665
- const outPath = import_path5.default.join(process.cwd(), game.outputDir, "math_config.json");
2642
+ const outPath = import_path4.default.join(process.cwd(), game.outputDir, "math_config.json");
2666
2643
  writeJsonFile(outPath, config);
2667
2644
  }
2668
2645
  return config;
2669
2646
  }
2670
2647
 
2671
2648
  // src/utils/setup-file.ts
2672
- var import_path6 = __toESM(require("path"));
2649
+ var import_path5 = __toESM(require("path"));
2673
2650
  function makeSetupFile(optimizer, gameMode) {
2674
2651
  const gameConfig = optimizer.getGameConfig();
2675
2652
  const optimizerGameModes = optimizer.getOptimizerGameModes();
@@ -2705,19 +2682,19 @@ function makeSetupFile(optimizer, gameMode) {
2705
2682
  `;
2706
2683
  content += `simulation_trials;${params.simulationTrials}
2707
2684
  `;
2708
- content += `user_game_build_path;${import_path6.default.join(process.cwd(), gameConfig.outputDir)}
2685
+ content += `user_game_build_path;${import_path5.default.join(process.cwd(), gameConfig.outputDir)}
2709
2686
  `;
2710
2687
  content += `pmb_rtp;${params.pmbRtp}
2711
2688
  `;
2712
- const outPath = import_path6.default.join(__dirname, "./optimizer-rust/src", "setup.txt");
2689
+ const outPath = import_path5.default.join(__dirname, "./optimizer-rust/src", "setup.txt");
2713
2690
  writeFile(outPath, content);
2714
2691
  }
2715
2692
 
2716
2693
  // src/optimizer/index.ts
2717
- var import_child_process2 = require("child_process");
2718
- var import_path7 = __toESM(require("path"));
2719
- var import_assert4 = __toESM(require("assert"));
2720
- var import_worker_threads3 = require("worker_threads");
2694
+ var import_child_process = require("child_process");
2695
+ var import_path6 = __toESM(require("path"));
2696
+ var import_assert5 = __toESM(require("assert"));
2697
+ var import_worker_threads4 = require("worker_threads");
2721
2698
  var Optimizer = class {
2722
2699
  gameConfig;
2723
2700
  gameModes;
@@ -2730,12 +2707,13 @@ var Optimizer = class {
2730
2707
  * Runs the optimization process, and runs analysis after.
2731
2708
  */
2732
2709
  async runOptimization({ gameModes }) {
2733
- if (!import_worker_threads3.isMainThread) return;
2710
+ if (!import_worker_threads4.isMainThread) return;
2734
2711
  const mathConfig = makeMathConfig(this, { writeToFile: true });
2735
2712
  for (const mode of gameModes) {
2736
2713
  const setupFile = makeSetupFile(this, mode);
2737
2714
  await this.runSingleOptimization();
2738
2715
  }
2716
+ console.log("Optimization complete. Files written to build directory.");
2739
2717
  }
2740
2718
  async runSingleOptimization() {
2741
2719
  return await rustProgram();
@@ -2759,7 +2737,7 @@ var Optimizer = class {
2759
2737
  }
2760
2738
  }
2761
2739
  const criteria = configMode.resultSets.map((r) => r.criteria);
2762
- (0, import_assert4.default)(
2740
+ (0, import_assert5.default)(
2763
2741
  conditions.every((c) => criteria.includes(c)),
2764
2742
  `Not all ResultSet criteria in game mode "${k}" are defined as optimization conditions.`
2765
2743
  );
@@ -2771,7 +2749,7 @@ var Optimizer = class {
2771
2749
  }
2772
2750
  gameModeRtp = Math.round(gameModeRtp * 1e3) / 1e3;
2773
2751
  paramRtp = Math.round(paramRtp * 1e3) / 1e3;
2774
- (0, import_assert4.default)(
2752
+ (0, import_assert5.default)(
2775
2753
  gameModeRtp === paramRtp,
2776
2754
  `Sum of all RTP conditions (${paramRtp}) does not match the game mode RTP (${gameModeRtp}) in game mode "${k}".`
2777
2755
  );
@@ -2786,9 +2764,9 @@ var Optimizer = class {
2786
2764
  };
2787
2765
  async function rustProgram(...args) {
2788
2766
  return new Promise((resolve, reject) => {
2789
- const task = (0, import_child_process2.spawn)("cargo", ["run", "--release", ...args], {
2767
+ const task = (0, import_child_process.spawn)("cargo", ["run", "--release", ...args], {
2790
2768
  shell: true,
2791
- cwd: import_path7.default.join(__dirname, "./optimizer-rust"),
2769
+ cwd: import_path6.default.join(__dirname, "./optimizer-rust"),
2792
2770
  stdio: "pipe"
2793
2771
  });
2794
2772
  task.on("error", (error) => {
@@ -2879,7 +2857,7 @@ var SlotGame = class {
2879
2857
  console.log("No tasks to run. Enable either simulation, optimization or analysis.");
2880
2858
  }
2881
2859
  if (opts.doSimulation) {
2882
- await this.runSimulation({ debug: opts.debug });
2860
+ await this.runSimulation(opts.simulationOpts || {});
2883
2861
  }
2884
2862
  if (opts.doOptimization) {
2885
2863
  await this.runOptimization(opts.optimizationOpts || { gameModes: [] });