From fe85b1579a73e1a250e37cefedf763dd2edb8366 Mon Sep 17 00:00:00 2001 From: soulrap <135220351+soulrap@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:38:43 +0300 Subject: [PATCH] derive bet modes from the RGS instead of the placeholder table `stateMeta.betModeMeta` defaults to a placeholder table (ANTE / SUPERANTE / SUPERSPIN / BONUS / SUPER) that describes no particular game, and nothing overrides it - none of the sample games do. Two things follow: - Modes the published math does not have are offered in the buy/activate dialog. Playing one is rejected by the RGS with ERR_VAL "invalid amount". - BONUS is declared at costMultiplier 100. A game whose bonus costs 200x shows the wrong price before the request is ever sent. /wallet/authenticate already returns the authoritative list as config.gameModes, and Authenticate.svelte already consumes config for betLevels and jurisdiction - it just ignores gameModes. Reconcile against it there: server modes win on identity and cost, presentation a game has set (assets, copy, type) is kept for the modes that survive. Co-Authored-By: Claude Opus 5 --- .../src/components/Authenticate.svelte | 19 ++++++++++- packages/state-shared/src/stateMeta.svelte.ts | 32 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/components-shared/src/components/Authenticate.svelte b/packages/components-shared/src/components/Authenticate.svelte index 61e7150be..b24573299 100644 --- a/packages/components-shared/src/components/Authenticate.svelte +++ b/packages/components-shared/src/components/Authenticate.svelte @@ -2,7 +2,15 @@ import { onMount, type Snippet } from 'svelte'; import { requestAuthenticate, requestReplay } from 'rgs-requests'; - import { stateUrlDerived, stateBet, stateConfig, stateModal, stateUi } from 'state-shared'; + import { + stateUrlDerived, + stateBet, + stateConfig, + stateMeta, + stateModal, + stateUi, + reconcileBetModeMeta, + } from 'state-shared'; import { API_AMOUNT_MULTIPLIER, MOST_USED_BET_INDEXES } from 'constants-shared/bet'; type Props = { children: Snippet }; @@ -66,6 +74,15 @@ stateConfig.betMenuOptions = stateConfig.betAmountOptions.filter((_, index) => MOST_USED_BET_INDEXES.includes(index), ); + + // Example of authenticateData.config.gameModes + // [ + // { "mode": "base", "costMultiplier": 1, "maxBet": 2000000000 }, + // { "mode": "bonus", "costMultiplier": 200, "maxBet": 2000000000 } + // ] + if (authenticateData.config?.gameModes?.length) { + stateMeta.betModeMeta = reconcileBetModeMeta(authenticateData.config.gameModes); + } } // round diff --git a/packages/state-shared/src/stateMeta.svelte.ts b/packages/state-shared/src/stateMeta.svelte.ts index de5d69c80..950754e62 100644 --- a/packages/state-shared/src/stateMeta.svelte.ts +++ b/packages/state-shared/src/stateMeta.svelte.ts @@ -59,3 +59,35 @@ export const stateMeta = $state({ export const stateMetaDerived = { betModeMetaList: () => Object.values(stateMeta.betModeMeta), }; + +/** + * Reconcile `betModeMeta` against the bet modes the RGS reports at authentication. + * + * `betModeMeta` defaults to a placeholder table that does not describe any + * particular game. Modes it lists that the RGS does not know are rejected with + * ERR_VAL "invalid amount" when played, and a costMultiplier that disagrees with + * the published math is wrong in the UI before it is ever sent. + * + * Server-reported modes win on identity and cost; presentation a game has already + * set (assets, copy, type) is kept for the modes that survive. + */ +export const reconcileBetModeMeta = ( + gameModes: { mode: string; costMultiplier: number }[], +): BetModeMeta => { + const reconciled: BetModeMeta = {}; + + gameModes.forEach(({ mode, costMultiplier }) => { + const key = mode.toUpperCase(); + const existing = stateMeta.betModeMeta[key] ?? stateMeta.betModeMeta[mode.toLowerCase()]; + + reconciled[key] = { + ...(existing ?? DEFAULT_BET_MODE_META.BASE), + mode: key, + costMultiplier, + // only infer when the game has not described this mode itself + type: existing?.type ?? (costMultiplier === 1 ? 'default' : 'buy'), + } as BetModeData; + }); + + return reconciled; +};