News · Studio

A prize the paytable called 5,000× was paying 705×

An independent second implementation found an integer overflow that every other check in the building agreed with.

All posts

One of our games has a money ladder topping out at 5,000× the stake. Compiled against the unit the resolver works in, that value is five billion, and the field holding it was a 32-bit signed integer, whose ceiling is a little over two billion. The value wrapped.

It wrapped to 705,032,704, which is 705.03×. The number was positive, it was under the round cap, and the game returned a plausible 96.34% on the mode in question instead of the 99.89% it should have. Nothing crashed and no figure looked odd.

Nothing in our own repository could have caught it. The conformance check compares the published ladder against the compiled ladder, and both sit above the defect: both said 5,000. The modelled return and the raw wins-over-stake figure were bit-identical, so the sentinel that compares them was comparing a number with itself. And the hash that guards the maths did not cover the resolver.

The second engine found it. It is a clean-room implementation, built from the written specification by someone who had not read the first one. It had been running for months and had been checking one mode of six; the day its coverage was widened to every mode a pack declares, it disagreed.

There are now three guards where there were none. Any figure that does not survive a round trip through the field that will hold it is refused at compile time, with the limit probed from a real array instead of read from a table of constants. The resolver is hashed. And an independent report that covers fewer modes than the pack declares now fails instead of reading as agreement. That last gap nearly cost us more than the overflow did.