Measured Behaviour
Published
A bonus engine has obvious failures everybody tests for: a bonus that will not credit, a stake that will not count, a withdrawal that will not unlock. It has one almost nobody tests for, because it is not a property of any bet - the WEIGHT each true pound of turnover carries. We measured what a rename does to it.
The multiplier is not the control
"Turn the bonus over 35 times" is the most quoted number in this industry and it is not what protects the bonus budget. The control is the table of contribution weights underneath it: slots count in full because the house edge is large, roulette a tenth and blackjack a twentieth, because a player covering most of a table can turn a bonus over at almost no cost to themselves.
We built a GBP50 bonus at 35x - a GBP1,750 requirement - and thirteen genuine betting sessions working through it. Roulette and blackjack for most of the turnover, one slots session, one live baccarat, one video poker. A GBP5 maximum bet while the bonus is open, one game barred outright, and a 168-hour expiry.
The correct run counts ten of the thirteen sessions. One is on the barred game, one contains a single bet over the maximum, one falls after expiry. Real turnover across the counted sessions is GBP2,590. Weighted turnover is GBP254.25. Against a GBP1,750 requirement that is nowhere near met, the withdrawal stays locked, and the bonus is never released.
Protected on five profiles with default settings, behaviour was identical every time, and the base column was measured twice and diffed identical.
A fallback that is a correct answer for the biggest category
Here is what makes this one survive inspection. When the library cannot find a weight for a game category, it does not throw and it does not use zero. It uses 100%. That is not laziness: slots genuinely contribute 100%, and slots are most of the catalogue, most of the revenue and most of the sessions on a real site.
So a missing weight table does not produce an obviously wrong number. It produces the number that is right for the largest category, applied to every category.
Renaming the property the weight table is passed under leaves the table in the bundle. It is still there, still correct, still five rows long. It is absent from the KEY the library reads it from, so the library falls back.
What renaming the contribution deck actually did
Nothing broke. Sessions stayed at thirteen, counted stayed at ten, voided stayed at three, and the same three sessions were voided for the same three reasons. Real turnover stayed at GBP2,590.00 to the penny. The multiplier still printed as 35x and the withdrawal was still gated on the requirement being met.
Weighted turnover went from GBP254.25 to GBP2,590.00. The requirement went from unmet to met, at 148.0% of it. The withdrawal went from LOCKED to ALLOWED, and the release timestamp went from never to hour 26 - about a day into a bonus the player should have spent a week clearing.
The counter that tells the story is the one no dashboard carries: the pounds of real turnover that did not count towards the requirement because they were staked on a discounted game. It went from GBP2,335.75 to GBP0.00. Both renaming presets produced byte-identical output, and the operator's own wagering check passed in both runs - it compares a figure this run computed against another figure this run computed.
Byte-identical to the fixture you would have built
We re-ran the same thirteen sessions with every one of them moved onto slots, keeping the barred game barred and the oversized bet oversized so the void reasons still fired.
That control matches the defective arm on every counter: GBP2,590.00 weighted, requirement met, withdrawal allowed, released at hour 26, GBP0.00 discounted. The only lines that separate the two runs are the reported weight table and the category label printed beside each session - neither of which is a figure anybody reconciles.
This is the eighth pass running in this series where an arm has landed on a negative control's figures, and the reason is always the same shape: the fallback IS a correct value, for the case the control was built out of. Slots are the first game anybody reaches for when they need a row of test data, and a slots-only fixture cannot distinguish a weight table from its absence.
A bigger multiplier does not save you
We measured the obvious mitigation. Losing the contribution table together with the multiplier is not worse in the way you would guess, and losing the table while keeping a large multiplier is not protective either.
The arithmetic is why. At true weights, GBP2,590 of turnover yields GBP254.25 towards the requirement - about a tenth. To make a 35x requirement bite through a 100% fallback you would need a multiplier ten times larger, which no market permits and no player would accept. The weights are doing roughly an order of magnitude of work, and the multiplier cannot be raised far enough to substitute for them.
The general form is worth carrying: when a control is a product of a headline number and a lookup table, the table is usually the larger factor, and it is always the one nobody reviews.
What to do about it
Make a missing weight an error, not a fallback. This is the same mitigation that keeps coming out of this series and it is worth having whatever you build with, because a dropped key over a serialisation hop, a new game category the table has not been updated for, and a hand-edited config all produce the same miss. Refusing to count a session whose category has no published weight is a one-line change and it fails closed.
Report the deck the library actually consulted, and assert it at start-up against the deck you supplied. A count of rows is enough to catch this; you do not need to diff the values.
Reconcile release against turnover composition rather than against the requirement alone. A bonus released on GBP2,590 of turnover that was 90% table games is a different event from one released on GBP2,590 of slots, and only the composition tells them apart.
On the build side, scope the renaming. Member renaming is opt-in and takes a regular expression, so a pattern that excludes the option keys you pass to an installed library keeps the rest of the bundle renamed - the mechanics are on the RenameMembers documentation. If the bundle in question is a browser game client, the wider packaging question is covered on our HTML5 game protection page.
Frequently asked questions
Does obfuscation break bonus wagering by default?
No. We protected the same bonus engine on five profiles - the ES5 target, the modern target, both emit-gate configurations and the string-encoding profile - and every one produced behaviour identical to the unprotected run, measured twice and diffed identical. The failure here comes from member renaming, which is opt-in and takes a pattern you choose.
What went wrong when the contribution table was renamed?
The library could no longer find a weight for any game category, so it fell back to 100% for all of them. That fallback is not arbitrary: slots really do contribute 100%. Weighted turnover went from GBP254.25 to GBP2,590.00 against a GBP1,750 requirement, and the withdrawal went from locked to allowed.
Did any count or total change?
No. Sessions stayed at thirteen, counted at ten, voided at three for the same three reasons, and real turnover at GBP2,590.00 to the penny. The multiplier still printed as 35x. Only the weighting moved, and with it the release.
How early was the bonus released?
At hour 26 of a bonus with a 168-hour expiry, against never in the correct run. The pounds of real turnover that did not count towards the requirement - the figure the weight table exists to produce - went from GBP2,335.75 to GBP0.00.
Why did our test fixture not catch it?
We re-ran the same thirteen sessions with all of them on slots. That control matches the defective arm on every counter, because 100% is the correct weight for slots and 100% is what a missing table falls back to. Only the reported weight table and the per-session category labels differ. A slots-only fixture cannot distinguish a contribution table from its absence.
Would a larger multiplier protect the bonus budget?
Not meaningfully. At true weights the turnover in our fixture yields about a tenth of its face value towards the requirement, so you would need a multiplier roughly ten times larger to compensate for a 100% fallback. The weights carry an order of magnitude more of the control than the multiplier does.
How do we keep the protection and avoid the defect?
Make a missing weight refuse the session rather than default to full contribution, assert the row count of the deck the library actually consulted at start-up, and reconcile releases against turnover composition. On the build side, scope RenameMembers so option keys handed to installed libraries are excluded - see the RenameMembers documentation.
Related reading