Free Rounds
Free rounds are bonus rounds funded by the operator. They are managed entirely by the Kalamba SDK Wrapper — the wrapper detects them in RGS responses, restricts the bet, injects the required payload into play requests and informs the Game Client about lifecycle changes. The Game Client does not talk to the RGS about free rounds directly.
The FreeRound shape
type FreeRound = {
conf: {
base: number
multiplier: number
numAwarded: number
}
data: {
numPlayed: number
win: number
numLeft: number
}
id: string
rejectable: boolean
skippable: boolean
status: 'PENDING' | 'ACTIVE' | 'FINISHED'
type: string
}
conf describes the awarded package (the fixed bet and the number of rounds), data describes the progress so far. rejectable and skippable tell whether the player is allowed to decline or skip the offer.
Lifecycle
Free rounds arrive in the freeRounds array of the open game and play responses. The wrapper inspects the first entry after the game reports playReady (following open game) and after every playCycleEnd, and acts on its status:
PENDING— the player has an offer waiting. The wrapper sendsfreeRoundsOfferwith theFreeRoundpayload.ACTIVE— free rounds are running. The wrapper activates them: it remembers the free roundid, injects{ freeRoundId }into every play request sent to the RGS, and sends restrictedlegalBetsandbetevents matching the free round configuration (conf.base/conf.multiplier). While free rounds are active the wrapper also stops deducting the bet from the displayed balance onplayCycleStart.FINISHED— the package has been played out. The wrapper sendsfreeRoundsCompletewith the finalFreeRoundpayload (includingdata.win), stops injectingfreeRoundId, and restores the originallegalBetsand defaultbetfrom the open game response.
Info
While free rounds are active, the Game Client MAY send freeRoundsPopup (no payload) to request the current session state. The wrapper answers with freeRoundsInfo carrying the current FreeRound.
sdk.send('freeRoundsPopup')
Player actions
The Game Client notifies the wrapper about the player's decision on an offer by sending freeRounds with a FreeRoundsAction payload:
type FreeRoundsAction = {
action: 'ACCEPT' | 'DECLINE' | 'SKIP'
id: string
}
sdk.send('freeRounds', { action: 'ACCEPT', id: freeRound.id })
DECLINE MUST only be offered to the player when rejectable is true, and SKIP only when skippable is true. On ACCEPT the wrapper activates the free rounds once the RGS confirms the action with freeRoundsResponse. If the RGS fails to process the action, the wrapper reports it like any other RGS error (see Error Handling).
showFreeRounds
By default (showFreeRounds: true) the wrapper renders its own free rounds popups for the offer, info and complete stages — see Wrapper Features.
If the Game Client wants to render these screens itself, it MUST send showFreeRounds: false in sdk.configure and MUST handle the freeRoundsOffer, freeRoundsInfo and freeRoundsComplete events on its own, including sending the freeRounds action for the offer.
sdk.on('freeRoundsOffer', freeRound => {
game.showFreeRoundsOffer(freeRound)
})
sdk.on('freeRoundsComplete', freeRound => {
game.showFreeRoundsSummary(freeRound)
})
Minimal Game Client requirements
Even with the wrapper popups enabled, every Game Client MUST:
- React to
legalBetsandbetevents at any time — the wrapper uses them to lock the bet to the free round configuration on activation and to restore it on completion (see Reacting to Events). - Treat the play request payload transparently — the
freeRoundIdis injected by the wrapper, the Game Client MUST NOT add, remove or depend on it.
No other free-rounds-specific logic is required in the Game Client.