Skip to content

Video Poker: winning payout overflows int at very large bets and triggers a false game-over #239

Description

@mishamyte

Summary

In Video Poker (apps_source_code/videopoker), the payout calculation multiplies the bet by the paytable multiplier in plain int arithmetic. At very large bets the product overflows, which (via signed-integer wraparound) makes the score negative and silently ends the game with the "You have run out of money!" screen - i.e. hitting the jackpot is reported as going bankrupt.

This is a pre-existing defect, independent of PR #237 (issue #163); it was surfaced while reviewing that change and is being tracked separately.

Location

apps_source_code/videopoker/poker.c:789-790

poker_player->score +=
    poker_player->bet * paytable[recognize(poker_player)];

bet and every paytable entry are int, so bet * paytable[...] is an int * int product.

Trigger

The largest multiplier is a Royal Flush at 800x (paytable[0]). With INT_MAX = 2147483647:

  • Royal Flush (x800): overflows once the bet is above ~2,684,355
  • Straight Flush (x50): above ~42.9M
  • Four of a Kind (x25): above ~85.9M

Because the bet is capped at the bank, this needs a bank of at least ~2.68M, reachable only after an extended winning streak (the starting bank is 1000). So it is an edge case - but it is real signed-integer overflow (undefined behavior), not merely a display glitch.

Impact

When the product overflows to a negative value:

  1. score becomes negative at poker.c:789-790.
  2. clamp_bet() (poker.c:793) then pins bet to that negative score.
  3. The if(poker_player->score <= 0) check (poker.c:799) routes the player to GameState = 4, the "You have run out of money!" game-over screen.

Net effect: a winning hand at a large enough bet ends the game as a bankruptcy.

Related note: score, bet and highscore are all int, so the bank can also overflow simply by accumulating winnings past INT_MAX, even without a single large multiply.

Suggested fix

Compute the payout in a wider type and saturate before storing back into the int score, e.g.:

int64_t win = (int64_t)poker_player->bet * paytable[recognize(poker_player)];
int64_t total = (int64_t)poker_player->score + win;
poker_player->score = total > INT32_MAX ? INT32_MAX : (int)total;

Alternatively, widen score/bet/highscore to a 64-bit type (and update the %d format specifiers in the draw callback), or cap the bank at a sane maximum. Since this app is third-party (fap_author="@PixlEmly"), a fix may want to be coordinated upstream.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    category/gamesApp category: gamespack/catalogApp in apps_source_code (catalog)status/third-partyApp is authored/maintained outside our team; coordinate fixes with upstreamtype/bugSomething isn't working

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions