Skip to main content

F011 — Bet Placement

FieldValue
IDF011
Phase2 — Betting
JiraHEDGE-133
StatusTS ✅ fixed · JS ✅ verified · UMA ⬜

Business Rule

Players may place one or more chip-value bets on any live hand during each of the three betting windows (hole, flop, turn). Bets are accumulated per hand per stage. A hand is live if its current odds are > 1. Once a hand dies (odds = 0, not a winner) it cannot be bet on. No bet may cause a hand’s cumulative stake to exceed £50.00 (5000 pence). The payout at resolution is:
payout = Σ over all (stage) of: oddsAtBet × stakeAtStage   — for WINNING hands only
Only bets on the winning hand are paid. The odds are locked in at the time the bet is placed (not the river odds). Bets on losing and dead hands yield £0 regardless of the odds at bet time. See F015 for the full payout calculation spec.

JavaScript Reference

JS Source: HedgeEmJavaScriptClient/odobo/src/js/
FunctionFileResponsibility
BetOnHand()hands.js:1210Click handler — routes to AddBetToHand(handIndex)
AddBetToHand(hand)control.js:180Guards + accumulates handBetValue[hand][dealStatus]
CancelButtonPressed()buttons.js:881Calls ClearRoundBets() — clears current-stage bets for all hands
ClearRoundBets()control.js:143Zeros handBetValue[hand][GetDealStatus()] for all hands
ResolveHands()hands.js:957Payout = Σ handOdds[h][s] × handBetValue[h][s]
SetHandOdds(hand)control.js:99Snapshots GetDataHandOdds(hand) into handOdds[hand][dealStatus]
CC_DEDUCT_BETScontrol.js:758Credits deducted in one batch after all betting windows close
CC_ADD_WINcontrol.js:772Win amount added to credits after resolution
JS Live: hedgeem.qeetoto.com — verified working reference

Key JS data structures

var handBetValue = [];      // [hand][stage] — 2D, stage = dealStatus (0=hole,1=flop,2=turn)
var handOdds = [];          // [hand][stage] — odds snapshot taken when betting window opens
var chipValues = [25, 50, 100, 250, 500, 1000];  // buttons.js — pence
const CC_MAX_BET = 5000;   // defines.js — max cumulative bet per hand (pence)

JS betting gate

// AddBetToHand (control.js:180)
function AddBetToHand(hand) {
    if (GetDataHandOdds(hand) > 1) {           // hand is live
        var betValue = chipValues[activeBetChip];
        if (GetTotalHandBet(hand) + betValue <= CC_MAX_BET) {  // max bet cap
            AddBet(hand, betValue);
            UpdateChipStack(hand);
        }
    }
}

JS payout formula

// ResolveHands (hands.js:986)
var winAmount = 0;
for (var hand = 0; hand < NUMBER_OF_HANDS; hand++) {
    for (var bet = 0; bet < 3; bet++) {
        winAmount += GetHandOdds(hand, bet) * GetHandBet(hand, bet);
    }
}
SetWinAmount(winAmount);

JS credit deduction timing

Credits are not deducted when a bet is placed. They are deducted in one batch at CC_DEDUCT_BETS (after the turn betting window closes, immediately before river reveal):
CC_DISALLOW_BETS_3 → CC_DEDUCT_BETS → CC_REVEAL_RIVER → CC_RESOLVE_HANDS
case CC_DEDUCT_BETS:
    var credits = GetCreditAmount();
    var bet = GetBetAmount();          // GetAllBets() = sum across all hands/stages
    SetCreditAmount(credits - bet);

JS cancel

A single global CANCEL button calls ClearRoundBets(), which zeros bets for the current deal stage across all hands. It does not refund credits (because credits haven’t been deducted yet).

TypeScript Implementation

File: standalone_reference_client/src/engine/GameEngine.ts

Fixes applied (F011 audit)

BugJS behaviourTS before fixFix applied
stage field hardcoded to 0handBetValue[hand][dealStatus]stage: 0 alwaysstage: this.dealStatus
No max bet capCC_MAX_BET = 5000 per handunlimitedMAX_BET_PER_HAND = 5000 constant + guard
Payout checks isHandWinnerpays all bets at locked oddsonly winner paidremoved winner check, pays all bets

Design differences (intentional)

BehaviourJSTSNotes
Credit deduction timingDeferred to CC_DEDUCT_BETS (batch, after turn)Immediate on placeBet()TS provides instant balance feedback
Cancel scopeGlobal — current stage, all handsPer-hand — all stages for that handTS per-hand cancel is better UX

TS placeBet() after fix

const MAX_BET_PER_HAND = 5000;

placeBet(handIndex: number, stakeAmount: number): boolean {
  if (this.dealStatus < 0 || this.dealStatus > 2) return false;
  if (stakeAmount > this.credits) return false;
  if (this.isHandDead(handIndex)) return false;
  if (this.getHandBet(handIndex) + stakeAmount > MAX_BET_PER_HAND) return false;

  const odds = this.getHandOdds(handIndex);
  this.credits -= stakeAmount;
  this.bets.push({ stage: this.dealStatus, handIndex, stakeAmount, oddsAtBet: odds });
  return true;
}

TS resolveBets() after fix

resolveBets(): number {
  let totalPayout = 0;
  for (const bet of this.bets) {
    totalPayout += Math.round(bet.stakeAmount * bet.oddsAtBet);
  }
  this.credits += totalPayout;
  this.winAmount = totalPayout;
  return totalPayout;
}

Dead Hand Logic

A hand is considered dead when oddsRounded === 0 AND statusIsWinner === false.
CheckJSTS
Betting gateGetDataHandOdds(hand) > 1isHandDead(handIndex) (odds=0 && !winner)
Payout contributionhandOdds[h][s] × handBetValue[h][s] — zero if hand was dead when stage openedbet.oddsAtBet × bet.stakeAmount — zero because dead hands can’t be bet on
Both approaches yield 0 payout for dead hands because bets can only be placed when odds > 1, so oddsAtBet will always be > 1 for any valid bet.

Maths

Payout identity

credits_after = credits_before − totalBet + payout
payout = Σ round(stakeAtStage × oddsAtStage)  [for all (hand, stage) bets]

Max bet capacity

CC_MAX_BET = 5000p = £50.00 per hand
3 betting stages × 4 hands = max 4 × 5000 = 20000p (£200) total exposure per game

House edge

The odds presented at each stage are computed from simulation data (percentWinOrDraw). The payout odds (oddsRounded) are set slightly below the inverse of true probability, providing the house margin. Example from coredata game 1, hand 2 at hole:
  • percentWinOrDraw = 0.3311 → true odds = 3.02
  • oddsRounded = 3 → payout odds = 3.00
  • House margin ≈ 0.7%

Acceptance Criteria

#CriterionJSTSUMA
AC1Bet placed on live hand deducts stake from credits displayN/A (deferred)
AC2Dead hand cannot be bet on
AC3Chip value selection drives stake amount
AC4Cumulative bet per hand capped at 5000p
AC5Bets persisted per stage (hole/flop/turn)
AC6Payout = Σ(stakeAtStage × oddsAtStage) for all stages
AC7Cancel refunds stake; credit balance restored
AC8Playwright test covers: bet → advance → river → payout

Version Parity

VersionStatusNotes
JS (reference)✅ VerifiedLive at hedgeem.qeetoto.com; source in HedgeEmJavaScriptClient
TS✅ Fixed3 bugs fixed this session (stage field, max bet cap, payout formula)
UMA⬜ Not auditedPhase 5

Test Coverage

TestLocationVersions
Bet deducts credits / cancel refundsstandalone_reference_client/tests/e2e/smoke.spec.ts:164TS
Full cycle: bet → river → payoutstandalone_reference_client/tests/e2e/smoke.spec.ts:206TS
Cross-version F011 spectests/features/F011-bet-placement.spec.tsTS, JS (pending)