Frosted Market Bonanza — XLSX to Runtime Math Guide

This document is based on the actual repository code, not on assumptions about slot math. It traces the path from the mathematician’s workbook to the runtime game JSON and then to the game logic that produces a spin result.

1. Executive summary

The real flow in this repo is:

Math team workbook ↓ raw XLSX in feg_parse-maths/rawMaths/frostedMarketBonanza ↓ parser in feg_parse-maths/src/commands/frosted-market-bonanza.commands.ts ↓ JSON output in feg_game_be/games/frostedmarketbonanza/src/maths/frosted-market-bonanza-R3.json ↓ plugin in feg_game_be/games/frostedmarketbonanza/src/plugin.ts ↓ engine in feg_game_be/games/frostedmarketbonanza/src/engine.ts ↓ logic in feg_game_be/games/frostedmarketbonanza/src/logic.ts ↓ response DTO in feg_game_be/games/frostedmarketbonanza/src/interfaces.ts

The key point is that this is not a generic Excel-to-engine pipeline. The parser reads very specific ranges from specific sheets and writes a game-specific JSON object. The game runtime loads that JSON and does not read the original XLSX at all.


2. Inventory of the raw math folder

The raw mathematician files are under:

Files found

FileExtensionMeaning in repoNotes
frosted_market_bonanza_96.5.xlsx.xlsxRaw source workbookLikely a prior version
frosted_market_bonanza_v2_96.5.xlsx.xlsxActive source workbook used by parserParser explicitly reads this file
~$frosted_market_bonanza_96.5.xlsxtemporary Excel lock fileTemporary file created by ExcelIgnore
~$frosted_market_bonanza_v2_96.5.xlsxtemporary Excel lock fileTemporary file created by ExcelIgnore

Which files look like source math files?

The two .xlsx files are the source math workbooks. The parser specifically reads:

The key line is:

const workbook = readMathFile('./rawMaths/frostedMarketBonanza/frosted_market_bonanza_v2_96.5.xlsx');

So the actual source workbook used by this repo is the v2 workbook.

Workbook sheet inventory

The workbook is a 7-sheet workbook. The actual sheet names are:

  1. Result
  2. Base Game
  3. Free Game feature
  4. Ante
  5. Ante Free Game feature
  6. Buy Bonus feature
  7. BaB Trigger

This was confirmed by reading the raw workbook XML directly from the .xlsx file; the parser then uses only selected sheets and ranges.

Important workbook summary

The Result sheet contains summary math values like RTP and hit-rate metrics.

Examples from the workbook:

  • Total Game RTP: 0.96475316
  • Base Game RTP: 0.62057222199999995
  • Free Games RTP: 0.344191674
  • Buy Bonus RTP: 0.93211143775000005

These are summary values in the workbook, but they are not the values the parser reads as part of the runtime config. The parser reads specific ranges on the feature sheets, not the Result summary sheet.


3. The actual Excel workbook and its meaning

The parser reads the workbook in a very direct, range-based way. It does not infer the layout generically.

The workbook structure matches a slot game with multiple modes:

  • Base Game
  • Ante
  • Free Game feature
  • Ante Free Game feature
  • Buy Bonus feature
  • BaB Trigger

The parser code is explicit about this:

worksheet = workbook.Sheets[workbook.SheetNames[1]]; // Base Game
worksheet = workbook.Sheets[workbook.SheetNames[2]]; // Free Game feature
worksheet = workbook.Sheets[workbook.SheetNames[5]]; // Buy Bonus feature
worksheet = workbook.Sheets[workbook.SheetNames[6]]; // BaB Trigger
worksheet = workbook.Sheets[workbook.SheetNames[3]]; // Ante
worksheet = workbook.Sheets[workbook.SheetNames[4]]; // Ante Free Game feature

This is a strong repository signal that the workbook is specifically designed for the same game across multiple modes, not a single universal template.

The sheet-by-sheet purpose as seen in the code

SheetParser useMeaning from workbook textCode evidence
ResultNot parsed for runtime mathSummary RTP / hit-rate sheetThe parser ignores this sheet
Base GameYesBase game, normal reel set, stack weights, pay table, nudge, symbol conversionBG_* ranges in parser
Free Game featureYesFree-spin reel sets, bomb multiplier weights, stack weightsFG_* ranges
AnteYesAnte variant, same base-game mechanics with altered bet and scatter treatmentANTE_* ranges
Ante Free Game featureYesAnte free-spin variantANTE_FG_* ranges
Buy Bonus featureYesBonus trigger / buy-bonus modeBB_* ranges
BaB TriggerYesBuy-bonus trigger reel set and scatter weightsBB_Trigger_* ranges

Important sheet-level facts from the workbook XML

From the workbook metadata and early rows:

  • Base Game sheet says rows = 5, reels = 6, base bet = 20, total bet = 20
  • Ante sheet says rows = 5, reels = 6, base bet = 25, total bet = 25
  • Free Game feature sheet says rows = 5, reels = 6, base bet = 1, cost to cover = 2000
  • This matches the runtime config in feg_game_be/games/frostedmarketbonanza/src/maths/frosted-market-bonanza-R3.json, where the game config sets layout.rows = 5, layout.columns = 6 and baseBet = 20, anteBaseBet = 25, bbBaseBet = 20.

What cannot be determined confidently?

The workbook names like RallyBracketsWeights, NonRallyBracketsWeights, and RallyNonRallyCascadeWeights are clearly used in code and not purely cosmetic, but the exact mathematical semantics of “Rally / Brackets / Cascade” cannot be confidently mapped from the workbook alone without domain-specific rule docs. The code treats them as weighted arrays used in the runtime logic via selectWeightedRandom and cascade refill helpers, but the business meaning is not obvious from the workbook alone.

This is an example of a place where the repository is clear about the data flow but not the original math-team intent.


4. Mathematical model used by the game

This game is not a standard paylines game. The runtime implementation clearly uses a cluster/cascade model.

Confirmed in code

The logic in feg_game_be/games/frostedmarketbonanza/src/logic.ts does the following:

const minClusterSize = 8;
const allBracketSymbols = ['H1', 'H2', 'H3', 'H4', 'L1', 'L2', 'L3', 'L4', 'L5'];

It then groups symbols and builds clusters with:

const symbolCounts = groupSymbolPositions({ reelView: currentReelView });
...
if (count >= minClusterSize) {
  clusters.push({ symbol, positions, size: count });
}

This is a cluster game. The win is based on matching symbol clusters of at least 8 positions, not a payline pattern.

Important mechanics actually present

The code and workbook confirm these features are active:

  • Cluster wins: yes
  • Cascade: yes
  • Free spins: yes
  • Scatter trigger: yes
  • Wilds: no generic wild symbol in the runtime config; the symbol map includes only STACK1, STACK2, SC, and ranked symbols
  • Multiplier bombs in free spins: yes
  • Nudge feature: yes
  • Symbol conversion feature: yes
  • Buy bonus: yes
  • Ante game: yes

The repository also documents a known issue in feg_game_be/docs/superpowers/handoff/2026-09-21-frosted-market-bonanza-freespin-trigger-bug.md, which confirms the game logic is not purely theoretical and that feature triggers can be silently dropped under conversion conditions.

How the math works in simple terms

The game begins with a reel board. Then:

  1. The base game chooses a reel set and generates a board.
  2. The game replaces stack symbols according to weight tables.
  3. It groups adjacent matching symbols into clusters.
  4. It pays on clusters with size >= 8.
  5. Winning symbols are removed and replaced by cascading symbols.
  6. It checks for scatter count; if the threshold is reached, it triggers free spins.
  7. In base game, optional nudge and symbol conversion can alter the final board if the conditions are met.
  8. In free spins, bomb multipliers are assigned and applied to the free-spin total.

5. Symbol information

The parser defines the symbol map at the top of feg_parse-maths/src/commands/frosted-market-bonanza.commands.ts:

private symbolsMap = Object.freeze({
  Pic1: 'H1',
  Pic2: 'H2',
  Pic3: 'H3',
  Pic4: 'H4',
  A: 'L1',
  K: 'L2',
  Q: 'L3',
  J: 'L4',
  '10': 'L5',
  BOMB: 'BN',
  Scatter: 'SC',
  STACK1: 'STACK1',
  STACK2: 'STACK2',
});

This is extremely important. The Excel layer uses labels like Pic1, Pic2, A, K, Q, J, 10, Scatter, STACK1, and STACK2. The runtime JSON uses canonical IDs such as H1, H2, H3, H4, L1, L2, L3, L4, L5, SC, BN, and STACK1 / STACK2.

Symbol classes in the repo

Excel labelRuntime IDRole
Pic1H1high-paying symbol
Pic2H2high-paying symbol
Pic3H3high-paying symbol
Pic4H4high-paying symbol
AL1low-paying symbol
KL2low-paying symbol
QL3low-paying symbol
JL4low-paying symbol
10L5low-paying symbol
ScatterSCscatter trigger
BOMBBNmultiplier bomb in free games
STACK1STACK1stack symbol
STACK2STACK2stack symbol

Where this matters in runtime

The JSON config stores symbolsMap and the logic uses it to convert worksheet labels into final runtime symbol IDs. The actual logic checks for:

  • configSymbols.scatter -> SC
  • configSymbols.multiplierSymbol -> BN

This is defined in the JSON config at the bottom of feg_game_be/games/frostedmarketbonanza/src/maths/frosted-market-bonanza-R3.json:

"symbols": {
  "scatter": "SC",
  "multiplierSymbol": "BN"
}

What about a generic wild symbol?

The code does not define a standard wild symbol for the game. Symbol replacement and cluster logic revolve around ranked symbols, stack symbols, scatter, and bomb multipliers. That is consistent with the repository code.


6. Reel / board information

The board is a 5x6 grid. This is confirmed in multiple places:

  • parser layout = '5x6'
  • workbook Base Game sheet states Rows = 5, Reels = 6
  • runtime config in JSON says layout.rows = 5, layout.columns = 6

The parser reads reels using helpers from feg_parse-maths/src/helpers/index.ts:

export const extractReels = (worksheet, startRange, numReels) => { ... }

The helper reads a selected range, extracts cells, filters nulls, and returns arrays of symbol tokens per reel. It then maps each raw symbol through this.symbolsMap.

Example

The parser extracts base game reel values from ranges such as:

BG_REEL1_RANGE: 'H15:H118'

and then reuses the same strategy across columns using extractReels(...) and getNextAlphabet(...) to move across reel columns.

This means the Excel workbook stores reel strips in a columnar format, and the parser converts them to arrays like:

ReelSet_1: [ ["H2","H2","L2",...], ["L2","L2","L1",...], ... ]

The runtime uses generateReelView(...) from the slot platform SDK to build the actual two-dimensional board from those reel strips.

Board interpretation

The board is effectively a 6-column by 5-row matrix. The code comments say:

  • select a reelstop from the selected reelset
  • create a 6x6 interface
  • display the bottom 5 rows of the selected 6x6 interface

This is consistent with a 6-column board where only the bottom 5 rows are visible and the top row is a hidden/selection context. The runtime code uses the board as a 5-row visible board, but the math workbook refers to a 6x6 internal interface.


7. Weights and the probability question

This is one of the most important parts.

The Excel weight values

The parser reads weight tables such as:

  • BG_STACK1_WEIGHTS_RANGE: 'S6:T9'
  • BG_STACK2_WEIGHTS_RANGE: 'V6:W10'
  • FG_BOMB_SET1_Weights_RANGE: 'AC21:AD33'
  • BB_TRIGGER_SC_Weights_RANGE: 'I23:J25'
  • NudgeFeature.TriggerWeights and NumReelsWeights

These values are eventually stored as arrays of [value, weight] pairs.

How they are read

The parser does:

mathJson.BG.STACK1_Weights = mapStackSymbol(
  getRangeData(worksheet, this.excelRangeObj.BG_STACK1_WEIGHTS_RANGE, false, false),
  this.symbolsMap,
);

The rest of the runtime uses them with functions like:

selectWeightedRandom(mathByGameType.NudgeFeature.TriggerWeights)

and

selectWeightedRandom(bombWeights!)

This confirms that the Excel weight is not a direct probability value in the math engine. It is a weighted random selection input. The runtime chooses a bucket according to the weight values, not by treating the raw numbers as direct probabilities.

Important distinction

Weight does not automatically equal probability in a single mathematical sense unless the author of the weight table explicitly normalizes it. The code treats the value as weight, and the random selector interprets it as weighted choice probability.

Chain of a weight

Excel weight table ↓ Parser reads range and stores [symbol, weight] ↓ selectWeightedRandom chooses among values ↓ Game logic decides a feature or symbol replacement ↓ Board outcome changes

Concrete example

The base game symbol-conversion feature uses two weight tables:

  • SymbolConversionFeature.LowSymbolWeights
  • SymbolConversionFeature.HighSymbolWeights

These produce a selected pair like selectedLow and selectedHigh, and then the code rolls TriggerWeights to decide if the conversion occurs.

This is implemented in feg_game_be/games/frostedmarketbonanza/src/symbol-conversion.ts.


8. Paytable and payout logic

The parser builds the paytable from the BG_PAY_TABLE_RANGE:

BG_PAY_TABLE_RANGE: 'B12:E20'

Then it calls:

mathJson.payTable = this.extractPayTable(worksheet, this.excelRangeObj.BG_PAY_TABLE_RANGE);

and later adds scatter pay data from:

BG_SC_PAY_RANGE: 'C23:E24'

How the paytable is transformed

The parser’s extractPayTable() method does this:

  • Takes the raw rows from the Excel table
  • Takes the first cell as the symbol identifier
  • Maps it through this.symbolsMap
  • Builds a compact array of length 12 for cluster sizes 1..12
  • Fills payout values for 8+, 9+, 10+, 11+, 12+ cluster sizes
if (cleanPayouts[0]) payArray[11] = cleanPayouts[0]; // 12+ symbols
if (cleanPayouts[1]) {
  payArray[9] = cleanPayouts[1]; // 10 symbols
  payArray[10] = cleanPayouts[1]; // 11 symbols
}
if (cleanPayouts[2]) {
  payArray[7] = cleanPayouts[2]; // 8 symbols
  payArray[8] = cleanPayouts[2]; // 9 symbols
}

That is the concrete translation from spreadsheet payout cells into the runtime paytable.

Win calculation in logic

In feg_game_be/games/frostedmarketbonanza/src/logic.ts:

const symbolPayouts = math.payTable[cluster.symbol];
const payIndex = Math.min(cluster.size - 1, (symbolPayouts?.length ?? 0) - 1);
const symbolPay = symbolPayouts?.[payIndex] ?? 0;
const winAmount = multiply(symbolPay, stakeValue) ?? 0;

This means:

  • find the cluster size
  • get the payout for that symbol at that cluster size
  • multiply by stake value
  • add the win to cascade total

Scatter pay

Scatter pay is applied after the cluster evaluation when the scatter count has reached the trigger threshold:

if (lastScCount >= fsTriggerCount) {
  const scPayouts = math.payTable[configSymbols.scatter];
  scatterPay = multiply(scPayouts?.[lastScCount - 1] ?? 0, stakeValue);
}

The final totalWin is cluster wins plus scatter pay.


9. RTP, hit rate, volatility, and distribution

The workbook’s Result sheet has summary statistics such as:

  • Total Game RTP: 0.96475316
  • Base Game RTP: 0.62057222199999995
  • Free Games RTP: 0.344191674
  • Buy Bonus RTP: 0.93211143775000005

The repo also tracks feature distributions in the simulation analyzer at feg_game_be/tools/game-tools/src/executors/simulate/analyzers/games/frosted-market-bonanza.analyzer.ts.

This analyzer tracks:

  • cluster wins
  • scatter pay wins
  • free-game wins
  • bomb multipliers
  • nudge triggers
  • symbol conversion triggers
  • win distribution
  • reel-stop frequencies

Are these values used at runtime?

The answer is mostly no, not directly.

  • The parser does not read the Result sheet for runtime game behavior.
  • The runtime JSON config contains game operational data, not the summary statistical workbook.
  • The simulation analyzer is for statistics and testing, not the live engine path.

So the RTP and hit-rate values appear to be:

  1. math-team summary values
  2. validation data
  3. simulation output
  4. not direct runtime inputs for the live slot engine

This distinction matters. They are not the same as the weights and reel-set arrays that the runtime actually consumes.


10. Parser flow in the repo

The real parser is in feg_parse-maths/src/commands/frosted-market-bonanza.commands.ts.

Parser entry point

The command is registered in feg_parse-maths/src/cli.module.ts and the CLI entry point is in feg_parse-maths/src/main.ts.

How the parser actually works

  1. Open the workbook with XLSX.readFile(...)
  2. Select a sheet using workbook.Sheets[workbook.SheetNames[n]]
  3. Use extractReels() to read the reel strip columns
  4. Use getRangeData() to read weight tables, cascade tables, and pay tables
  5. Apply this.symbolsMap to map spreadsheet labels to canonical runtime identifiers
  6. Write the final JSON object to ./output/frosted-market-bonanza-R3.json

The final write is:

writeFileSync(this.outputJsonFile, JSON.stringify(mathJson, null, 2), 'utf8');

Important parser helper functions

From feg_parse-maths/src/helpers/index.ts:

  • readMathFile(filePath) → reads workbook
  • getRangeData() → reads an Excel range into plain JS data
  • extractReels() → extracts reel strips from a range
  • mapStackSymbol() → remaps stack symbol labels using symbolsMap

This is the real conversion layer from notebook-style spreadsheet to runtime JSON.


11. Excel → code mapping

This is the most practical mapping for a new developer.

Excel locationParser usageRuntime propertyUsed by
Base Game sheet, H15:H118extractReels()BG.ReelSet_1base game board generation
Base Game sheet, S6:T9mapStackSymbol(getRangeData(...))BG.STACK1_Weightsstack replacement
Base Game sheet, V6:W10mapStackSymbol(getRangeData(...))BG.STACK2_Weightsstack replacement
Base Game sheet, B12:E20extractPayTable()payTablecluster wins and scatter pay
Base Game sheet, C23:E24getRangeData()payTable.SCscatter payout
Base Game sheet, AN14:AO16getRangeData()BG.NudgeFeature.TriggerWeightsnudge trigger decision
Base Game sheet, AQ14:AR16getRangeData()BG.NudgeFeature.NumReelsWeightsnudge reel count
Base Game sheet, AU15:AV16getRangeData()BG.SymbolConversionFeature.TriggerWeightsconversion trigger
Base Game sheet, AT20:AU23mapStackSymbol(getRangeData(...))BG.SymbolConversionFeature.HighSymbolWeightsconversion target
Base Game sheet, AW20:AX24mapStackSymbol(getRangeData(...))BG.SymbolConversionFeature.LowSymbolWeightsconversion source
Free Game feature sheet, AC6:AD7getRangeData()FG.ReelSelectionWeightsfree-spin reel set selection
Free Game feature sheet, AC21:AD33getRangeData()FG.BOMB_SET1_Weightsbomb multiplier values
Free Game feature sheet, AF21:AG33getRangeData()FG.BOMB_SET2_Weightsbomb multiplier values
BaB Trigger sheet, B4:B39extractReels()BB_Trigger.ReelSet_1buy-bonus trigger board
BaB Trigger sheet, I23:J25getRangeData()BB_Trigger.SC_Weightsscatter weight for buy bonus

This is the practical mapping a developer can use when someone says “I see a value in the workbook” and wants to know where it lands in the code.


12. Final parsed math object structure

The final shape created by the parser is in feg_game_be/games/frostedmarketbonanza/src/interfaces.ts. The root math config includes this structure:

export interface FrostedMarketBonanzaMathConfig {
  BG: BaseGameMathConfig;
  FG: FreeGameMathConfig;
  BB: FreeGameMathConfig;
  ANTE: BaseGameMathConfig;
  ANTE_FG: FreeGameMathConfig;
  BB_Trigger: BbTriggerMathConfig;
  payTable: Record<string, number[]>;
  maxWin: number;
  symbolsMap: Record<string, string>;
  config: {
    layout: { rows: number; columns: number };
    symbols: { scatter: string; multiplierSymbol: string };
    features: {
      freeSpinsTrigger: { minScatters: number; initialCount: number };
      freeSpinsRetrigger: { minScatters: number; additionalSpins: number };
    };
    maxWinMultiplier: number;
    maxWinBbMultiplier: number;
    baseBet: number;
    bbBaseBet: number;
    anteBaseBet: number;
  };
}

The JSON file mirrors this structure and is the runtime source of truth.

Important property explanations

PropertyTypeMeaningSource
BGobjectBase-game math blockworkbook Base Game
FGobjectFree-game math blockworkbook Free Game feature
BB_TriggerobjectBuy-bonus trigger modeworkbook BaB Trigger
ANTEobjectAnte-mode mathworkbook Ante
payTableRecord<string, number[]>Cluster payout tableparser pay table range
symbolsMapmapRaw spreadsheet labels → runtime IDsparser symbolsMap
config.layoutobjectrows and columnsworkbook and runtime config
config.featuresobjectScatter trigger countsJSON config
maxWinMultipliernumberMax win config capruntime config

13. Where the parsed data goes after parsing

The actual path is:

Excel workbook ↓ parser writes frosted-market-bonanza-R3.json ↓ FrostedMarketBonanzaPlugin.onLoad() reads the JSON ↓ plugin.getMath(mode) loads the selected mode ↓ engine.executeSpin(...) or engine.executeFeature(...) uses the config ↓ logic.spin(...) / logic.runFeature(...) applies the rules ↓ SpinResult is returned to the platform

Concrete file path chain


14. Request → response connection

The runtime game input is defined at the top of the interfaces file.

Request fields

The GameSpinInput includes:

  • betAmount
  • ante?: boolean
  • buyBonus?: boolean
  • combination?: number[]
  • devMode?: boolean
  • maxWin?: number
  • forceNudgeFeature?: string
  • forceNudgeReels?: number
  • forceSymbolConversion?: boolean
  • forceBonusMultiplier?: number

The plugin takes the API input and builds a game-specific view:

const spinInput: GameSpinInput = {
  betAmount,
  ante,
  buyBonus: isBuyBonus,
  combination,
  devMode,
  maxWin: rgsMaxWin,
  forceNudgeFeature,
  forceNudgeReels,
  forceSymbolConversion,
  forceBonusMultiplier,
};

Response fields

The actual game result object is SpinResult in feg_game_be/games/frostedmarketbonanza/src/interfaces.ts. It includes:

  • cascadeData
  • bet
  • totalWin
  • baseWin
  • nextFeature
  • pendingFeature
  • featureResults
  • multiplier
  • lineWin
  • scatterPay
  • reelModifiers

This is what the platform ultimately returns to a caller.


15. Full spin flow example

The best logical example is a normal base spin.

Step 1: request enters the plugin

The game entry point is feg_game_be/games/frostedmarketbonanza/src/plugin.ts:

const result = this.engine.executeSpin(spinInput, math);

Step 2: engine chooses the correct mode

In feg_game_be/games/frostedmarketbonanza/src/engine.ts:

const gameType = buyBonus ? 'BB_Trigger' : ante ? 'ANTE' : 'BG';

Then it picks the relevant reel strips.

Step 3: reel view generation

const { reelView, reelStops: capturedReelStops } = generateReelView({
  rows: layout.rows,
  columns: layout.columns,
  reelStrips,
  cheatCombination: devMode && combination ? combination : undefined,
});

Step 4: logic evaluates clusters and scatter triggers

The real game outcome is in feg_game_be/games/frostedmarketbonanza/src/logic.ts:

  • determine gameType
  • load mathByGameType
  • replace stack symbols
  • group symbols and find clusters
  • evaluate cluster payouts
  • apply scatter triggers
  • maybe trigger nudge or symbol conversion
  • maybe trigger free spins

Step 5: feature evaluation

If the scatter count reaches the threshold, the logic sets:

spinResult.nextFeature = 'freespins';
spinResult.pendingFeature = features.freeSpinsTrigger.initialCount;

Then the game enters runFeature(), which selects a free game reel set and runs additional spins.

Step 6: response

The plugin returns:

return {
  totalWin: result.totalWin,
  featureTriggered: result.nextFeature != null,
  gameData: result,
  baseWinAmount: result.totalWin,
  bonusWinAmount: 0,
  bonusRoundCount: 0,
};

16. One Excel value through to the game result

A good example is the scatter trigger threshold from the workbook and runtime config.

Excel value

In the workbook config, the free-spin trigger is effectively controlled by scatter count thresholds and feature weights. The runtime JSON declares:

"freeSpinsTrigger": {
  "minScatters": 4,
  "initialCount": 10
},
"freeSpinsRetrigger": {
  "minScatters": 3,
  "additionalSpins": 5
}

Path through the system

  1. Excel workbook defines scatter logic.
  2. Parser reads the relevant tables and stores the config values in the JSON.
  3. Runtime plugin loads the JSON.
  4. Logic checks:
if (lastScCount >= fsTriggerCount) {
  spinResult.nextFeature = 'freespins';
}
  1. The result populates nextFeature and featureResults.
  2. The platform returns featureTriggered: true and the game state is persisted in gameData.

This is a good example of how a number in the spreadsheet becomes a real gameplay decision in the backend.


17. Important math functions

FrostedMarketBonanzaLogic.spin()

File: feg_game_be/games/frostedmarketbonanza/src/logic.ts

Purpose: main spin resolver for base game and free spins.

Input: total bet, math config, reel view, game mode flags, max win, optional forced feature inputs.

Processing:

  • chooses game type
  • replaces stack symbols
  • groups symbol positions
  • calculates cluster wins
  • handles cascade refill
  • checks scatter trigger
  • may trigger nudge / conversion / free spins
  • applies win cap

Output: SpinResult object including wins, next feature, modifiers, and state.

FrostedMarketBonanzaEngine.executeSpin()

File: feg_game_be/games/frostedmarketbonanza/src/engine.ts

Purpose: build the reel board and orchestrate a base spin.

Important behavior:

  • selects reel strips by game type
  • generates reel view using generateReelView()
  • runs logic.spin()
  • if symbol conversion is triggered, reruns the spin on converted board

applySymbolConversion()

File: feg_game_be/games/frostedmarketbonanza/src/symbol-conversion.ts

Purpose: passive conversion from selected low symbol to selected high symbol.

Important behavior:

  • chooses low and high symbols
  • checks if enough symbols are present
  • uses trigger weight table
  • replaces cells and records changed positions

FrostedMarketBonanzaLogic.runFeature()

File: feg_game_be/games/frostedmarketbonanza/src/logic.ts

Purpose: resolves the free-spin feature continuation.

Important behavior:

  • selects free-spin reel set
  • runs spinFreeGame()
  • applies bomb multipliers
  • updates feature totals and pending spins
  • closes feature when counters drain or win cap is hit

18. Features actually present

Scatter free spins

  • Trigger: at least minScatters on a board
  • Implementation: lastScCount >= fsTriggerCount
  • Relevant config: freeSpinsTrigger in the JSON
  • Runtime logic: spinResult.nextFeature = 'freespins'

Nudge feature

  • Trigger: non-winning base spin with exactly 3 scatters and a weighted nudge decision
  • Relevant files: logic.ts, BG.NudgeFeature, ANTE.NudgeFeature
  • Runtime effect: modifies a few reels and optionally adds a scatter

Symbol conversion feature

  • Trigger: non-winning base spin where line win = 0 and nudge did not fire
  • Relevant files: symbol-conversion.ts, engine.ts, logic.ts
  • Runtime effect: converts selected low symbols into high symbols and sometimes creates a win or a feature path

Bomb multipliers in free games

  • Trigger: bomb (BN) symbol appears in free spins
  • Relevant config: BOMB_SET1_Weights, BOMB_SET2_Weights
  • Runtime effect: multiplier is applied to totals and pushed onto the cascade data

Buy bonus / BB trigger

  • Trigger: buy bonus mode uses BB_Trigger reel set
  • Relevant logic: gameType = 'BB_Trigger'
  • Runtime effect: scatter placement and buy-bonus trigger path

19. Tests and validation evidence

There are no dedicated unit tests for Frosted Market Bonanza in the repo that directly assert specific spin outcomes. However, the repo does contain validation and simulation tooling:

These files are extremely valuable because they tell us:

  • the analyzer tracks cluster wins and scatter payouts,
  • the analyzer tracks bomb multipliers and nudge behavior,
  • the repo has a known free-spin trigger bug after symbol conversion,
  • the actual code behavior is more nuanced than the spreadsheet labels alone suggest.

20. Debugging guide for a new developer

If you need to follow one spin through the engine, the best breakpoints are:

  1. feg_game_be/games/frostedmarketbonanza/src/plugin.ts — start at spin()
  2. feg_game_be/games/frostedmarketbonanza/src/engine.ts — inspect executeSpin()
  3. feg_game_be/games/frostedmarketbonanza/src/logic.ts — inspect spin()
  4. groupSymbolPositions() and removeWinningSymbols() — see how clusters are built and removed
  5. selectWeightedRandom() — inspect the feature weights used to decide floats/transformations
  6. runFeature() — inspect the free-spin continuation state

Key fields to inspect while debugging:

  • reelView
  • spinResult.cascadeData
  • spinResult.nextFeature
  • spinResult.featureResults
  • spinResult.scatterPay
  • spinResult.totalWin
  • spinResult.reelModifiers
  • pendingFeature
  • lineWin
  • multiplier

21. Questions for the math team / unknowns

Some things are clear in code but not fully interpretable without the math team:

1. Meaning of RallyBracketsWeights and NonRallyBracketsWeights

  • Found in workbook ranges and parser JSON fields
  • The code passes them through to the runtime but does not explain their business meaning
  • Ask: “What specific symbol pattern or bracket logic do these weights represent?”

2. Meaning of RallyNonRallyCascadeWeights

  • The code parses them but does not show a direct business meaning in the game logic
  • Ask: “What exact cascade path does each weight bucket represent?”

3. Symbol conversion trigger exact business rule

  • The code implements the actual logic, but the workbook wording does not clearly explain the intended rule beyond the general logic
  • Ask: “Is the conversion meant to be a pure passive feature or a recovery mechanic?”

4. Nudge rule semantics

  • The code clearly implements withScatter/withoutScatter behavior, but the dataset does not tell us the original intended business condition in plain English
  • Ask: “What exact board situation should trigger the nudge, and when is it intentionally suppressed?”

22. Final cheat sheet

Frosted Market Bonanza — Quick Reference


23. Bottom line

The actual flow in this repository is not a magical Excel-to-engine conversion. It is a deliberate, range-based transformation pipeline:

  • the source math workbook is specific and mode-based,
  • the parser reads exact named ranges,
  • the parser maps symbols into canonical IDs,
  • the runtime loads the generated JSON,
  • the game logic applies cluster rules, cascades, scatters, and feature logic,
  • the player receives a SpinResult object with the final outcome.

The most important rule for a new developer is: trust the parsed JSON and the runtime code, not the workbook labels alone. The codebase is the source of truth about what the game actually does.

Built with LogoFlowershow