Skip to main content

Overview

Every simulation follows a deterministic lifecycle. The create_books() function orchestrates the full batch: it assigns distribution criteria to simulation numbers, then calls run_spin() once per simulation. Each simulation writes its events to the books file and its payout multiplier to the lookup table.
1

Assign criteria to simulation numbers

Before any simulations run, create_books() reads the BetMode distributions defined in GameConfig and pre-assigns a criteria label (e.g. basegame, freegame, winCap, 0) to each simulation number according to the configured quotas. This is done once per bet mode.
2

Seed the RNG

run_spin() calls self.reset_seed(sim), which sets Python’s random seed to sim + 1. Because the seed is derived from the simulation number, every simulation is fully reproducible — re-running simulation 58 will always produce identical results.
3

Enter the repeat loop

self.repeat is set to True at the start of run_spin(). The loop continues until the spin satisfies the distribution criteria assigned to this simulation number. Each iteration calls self.reset_book(), which resets all local simulation variables and sets self.repeat = False. If the final result does not meet the criteria, self.check_repeat() sets self.repeat = True again and the loop retries.
4

Run the spin

Inside the loop, the three core phases execute: calculate the board state, update the wallet manager, and emit events. If a freespin trigger condition is met, run_freespin() is called before the final win is evaluated.
5

Verify distribution criteria

self.check_repeat() inspects the final payout against any win_criteria defined for this simulation’s assigned distribution, and checks whether a freespin was triggered if force_freegame is set. If either check fails, self.repeat is set back to True.
6

Imprint wins

Once self.repeat remains False after check_repeat(), self.imprint_wins() saves the simulation result: the events are added to the in-memory library, the payout multiplier is recorded, and cumulative win totals are updated for runtime RTP reporting.

RNG seeding

Each simulation is seeded with its own simulation number to ensure reproducibility:
This means that running the simulator twice with the same num_sim_args will produce identical books files. It also makes it straightforward to reproduce any specific result for debugging.

The repeat loop

The repeat mechanism ensures that pre-assigned distribution criteria are honoured. The loop works as follows:
  • run_spin() sets self.repeat = True before the loop begins.
  • reset_book() sets self.repeat = False at the start of each iteration, resetting all simulation state.
  • At the end of the spin, check_repeat() re-evaluates whether the result satisfies the assigned criteria. If not, it sets self.repeat = True and the loop retries with a fresh board.
A high repeat count warning is emitted if a simulation retries more than 1,000 times, which typically indicates an overly restrictive distribution criteria or misconfigured reels.

The run_spin() flow

The three phases inside the loop are:
  1. Calculate board state — draw symbols from reel strips, evaluate win combinations, determine any special symbol actions.
  2. Update wallet manager — call win_manager.update_spinwin() with the current spin win, and win_manager.update_gametype_wins() once all basegame actions are complete.
  3. Emit events — append JSON event objects to self.book for each state change (board reveal, win info, win counter updates, freespin trigger, etc.).

The run_freespin() flow

The freespin loop follows the same three-phase structure as run_spin(). The tot_fs counter may be increased by retriggers during the loop. win_manager.update_gametype_wins() is called at the end of each individual freespin so that basegame and freegame win contributions are tracked separately.

Simulation output

At the end of a successful simulation (after self.repeat remains False), two outputs are written:
  • Books — the complete list of events for this simulation is stored in self.library[sim + 1]. When all simulations in a batch are complete, the library is written to library/books/ or library/books_compressed/ as a JSONL file.
  • Lookup table — the payoutMultiplier for this simulation is appended to the lookup table CSV with an initial weight of 1.

imprint_wins()

imprint_wins() is the final step in every simulation. It:
  1. Processes all self.record() calls made during the spin into the recorded_events dictionary (used for force files and PAR sheet generation).
  2. Saves the completed book to self.library.
  3. Appends the payout multiplier to the internal payout list.
  4. Calls win_manager.update_end_round_wins() to accumulate running RTP totals printed to the terminal as threads finish.