@stakeplate/core 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/audio.d.ts +1 -1
- package/dist/{index-BXD-eK1I.d.ts → index-BpPekNWF.d.ts} +2 -2
- package/dist/index.d.ts +12 -6
- package/dist/index.js +22 -10
- package/dist/index.js.map +1 -1
- package/dist/{network-pBW8CX1o.d.ts → network-CBxKQzeb.d.ts} +44 -5
- package/dist/rgs.d.ts +2 -2
- package/dist/rgs.js +38 -5
- package/dist/rgs.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,31 @@ config.rules = built.menu; // → the white HTML menu
|
|
|
55
55
|
config.socialMessages = { en: built.socialEn }; // auto-derived social wording
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
## Networking — real RGS vs. the mock
|
|
59
|
+
|
|
60
|
+
`createStakeGame` picks the transport from the **launch URL**, so the same build runs
|
|
61
|
+
locally and on Stake with no code change:
|
|
62
|
+
|
|
63
|
+
| Launch | Transport |
|
|
64
|
+
|---|---|
|
|
65
|
+
| a real `rgs_url` is present (a Stake launch, incl. the dashboard's "local redirect") | the **real Stake RGS** — authenticates with the launch `sessionID`, pulls the real balance/config, spins + buys with real requests |
|
|
66
|
+
| `?demo=true` **with** a real `rgs_url` (Stake **fun-play**) | still the **real RGS** — `demo` is a demo *wallet*, not a fake backend |
|
|
67
|
+
| no `rgs_url` (bare `pnpm dev`), or `?mock=true` | the in-process **mock RGS** |
|
|
68
|
+
|
|
69
|
+
So a game gates its local dev mock on **`!isStakeLaunch()`** (a `readRuntime` helper) and
|
|
70
|
+
passes `network:` only in that case; a real launch then falls through to the real RGS. The
|
|
71
|
+
Stake "local redirect" opens a tab like
|
|
72
|
+
`…?sessionID=…&rgs_url=rgsd.stake-engine.com&lang=en¤cy=USD&demo=true` — and that
|
|
73
|
+
authorizes + plays against `rgsd.stake-engine.com`.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { createStakeGame, isStakeLaunch } from '@stakeplate/core';
|
|
77
|
+
createStakeGame({
|
|
78
|
+
/* … */
|
|
79
|
+
...(isStakeLaunch() ? {} : { network: myLocalMock }), // real RGS on a Stake launch
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
58
83
|
## Subpaths
|
|
59
84
|
|
|
60
85
|
- `@stakeplate/core` — the one-call API + engine (turbo, autoplay, buy-features).
|
package/dist/audio.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SfxBus, c as createGameAudio, d as bindInputSounds, f as MixerGroup, i as MusicBus, l as InputSoundMap, m as bindMixerToHud, n as GameAudio, o as SoundEntry, p as MixerLike, r as GameAudioOptions, s as bindAudioToHud, t as BusName, u as InputSoundOptions } from "./index-
|
|
1
|
+
import { a as SfxBus, c as createGameAudio, d as bindInputSounds, f as MixerGroup, i as MusicBus, l as InputSoundMap, m as bindMixerToHud, n as GameAudio, o as SoundEntry, p as MixerLike, r as GameAudioOptions, s as bindAudioToHud, t as BusName, u as InputSoundOptions } from "./index-BpPekNWF.js";
|
|
2
2
|
export { BusName, GameAudio, GameAudioOptions, type InputSoundMap, type InputSoundOptions, type MixerGroup, type MixerLike, MusicBus, SfxBus, SoundEntry, bindAudioToHud, bindInputSounds, bindMixerToHud, createGameAudio };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { m as Round } from "./protocol-C1KamF3t.js";
|
|
2
|
-
import { t as NetworkManager } from "./network-
|
|
2
|
+
import { t as NetworkManager } from "./network-CBxKQzeb.js";
|
|
3
3
|
import { n as RootStore } from "./index-BBlN97RY.js";
|
|
4
4
|
import { a as Ticker, t as HudPort } from "./hud-port-DNfotn4U.js";
|
|
5
5
|
import { BootedHud } from "@open-slot-ui/pixi";
|
|
@@ -354,4 +354,4 @@ declare function bindAudioToHud(audio: GameAudio, hud: BootedHud, opts?: {
|
|
|
354
354
|
}): () => void;
|
|
355
355
|
//#endregion
|
|
356
356
|
export { TurboState as A, modeCostOf as C, roundInfo as D, RoundInfo as E, DEFAULT_TURBO_SPEEDS as O, ModeConfig as S, InterpretBook as T, FSM as _, SfxBus as a, PhaseContext as b, createGameAudio as c, bindInputSounds as d, MixerGroup as f, AudioValue as g, AudioPort as h, MusicBus as i, TurboClock as k, InputSoundMap as l, bindMixerToHud as m, GameAudio as n, SoundEntry as o, MixerLike as p, GameAudioOptions as r, bindAudioToHud as s, BusName as t, InputSoundOptions as u, LoaderPort as v, GameRound as w, GameConfig as x, Phase as y };
|
|
357
|
-
//# sourceMappingURL=index-
|
|
357
|
+
//# sourceMappingURL=index-BpPekNWF.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as Balance, c as JurisdictionConfig, d as ReplayParams, f as RgsConfig, h as roundEvents, i as BOOK_AMOUNT_MULTIPLIER, l as PlayRequest, m as Round, n as AuthenticateRequest, o as EndRoundRequest, p as RgsError, r as AuthenticateResponse, s as EndRoundResponse, t as API_AMOUNT_MULTIPLIER, u as PlayResponse } from "./protocol-C1KamF3t.js";
|
|
2
|
-
import { a as MockOptions, c as RuntimeConfig, i as MockNetworkManager, l as
|
|
2
|
+
import { a as MockOptions, c as RuntimeConfig, d as urlParam, i as MockNetworkManager, l as isStakeLaunch, n as PlayArgs, o as ScriptedRound, r as createNetwork, s as ReplayLaunch, t as NetworkManager, u as readRuntime } from "./network-CBxKQzeb.js";
|
|
3
3
|
import { i as UiStore, n as RootStore, r as SessionStore, t as BalanceStore } from "./index-BBlN97RY.js";
|
|
4
4
|
import { a as Ticker, i as RealTicker, n as ReplayInfo, r as InstantTicker, t as HudPort } from "./hud-port-DNfotn4U.js";
|
|
5
|
-
import { A as TurboState, C as modeCostOf, D as roundInfo, E as RoundInfo, O as DEFAULT_TURBO_SPEEDS, S as ModeConfig, T as InterpretBook, _ as FSM, b as PhaseContext, g as AudioValue, h as AudioPort, k as TurboClock, l as InputSoundMap, o as SoundEntry, r as GameAudioOptions, v as LoaderPort, w as GameRound, x as GameConfig, y as Phase } from "./index-
|
|
5
|
+
import { A as TurboState, C as modeCostOf, D as roundInfo, E as RoundInfo, O as DEFAULT_TURBO_SPEEDS, S as ModeConfig, T as InterpretBook, _ as FSM, b as PhaseContext, g as AudioValue, h as AudioPort, k as TurboClock, l as InputSoundMap, o as SoundEntry, r as GameAudioOptions, v as LoaderPort, w as GameRound, x as GameConfig, y as Phase } from "./index-BpPekNWF.js";
|
|
6
6
|
import { StakeNetworkManager, StakeNetworkOptions } from "./rgs.js";
|
|
7
7
|
import { BootedHud } from "@open-slot-ui/pixi";
|
|
8
8
|
import { CurrencySpec } from "@open-slot-ui/core";
|
|
@@ -202,12 +202,18 @@ declare const EXTRA_DECIMALS: Readonly<Record<string, {
|
|
|
202
202
|
decimals: number;
|
|
203
203
|
symbol: string;
|
|
204
204
|
}>>;
|
|
205
|
+
/** Symbol-only fixes required by Stake's RGS-communication approval guidelines, layered on
|
|
206
|
+
* top of the @open-slot-ui table (which is otherwise correct on precision + symbol side).
|
|
207
|
+
* Danish Krone must read "KR" — the lib ships lowercase "kr". Taiwan Dollar already resolves
|
|
208
|
+
* to the accepted "NT$", so it needs no override. */
|
|
209
|
+
declare const SYMBOL_OVERRIDES: Readonly<Record<string, string>>;
|
|
205
210
|
/**
|
|
206
|
-
* `resolveCurrency`, but with the missing 3-decimal dinars/rials patched in
|
|
207
|
-
* everywhere a currency code becomes a
|
|
208
|
-
*
|
|
211
|
+
* `resolveCurrency`, but with the missing 3-decimal dinars/rials patched in and the
|
|
212
|
+
* approval-mandated symbol fixes applied. Use this everywhere a currency code becomes a
|
|
213
|
+
* `CurrencySpec` so precision + symbol are correct for every code — `createStakeGame`
|
|
214
|
+
* routes all of its resolution through here.
|
|
209
215
|
*/
|
|
210
216
|
declare function currencyFor(code: string): CurrencySpec;
|
|
211
217
|
//#endregion
|
|
212
|
-
export { API_AMOUNT_MULTIPLIER, AudioPort, AudioSpec, AudioValue, AuthenticateRequest, AuthenticateResponse, BOOK_AMOUNT_MULTIPLIER, Balance, BalanceStore, BeatClock, BeatOptions, CreateStakeGameOptions, DEFAULT_TURBO_SPEEDS, EXTRA_DECIMALS, EndRoundRequest, EndRoundResponse, FSM, FeatureItem, GameConfig, GameLoader, GameRound, GameSnapshot, HudPort, IdlePhase, InstantTicker, InterpretBook, JurisdictionConfig, LoaderConfig, LoaderPort, MockNetworkManager, MockOptions, ModeConfig, NetworkManager, Phase, PhaseContext, PlayArgs, PlayRequest, PlayResponse, PresentPhase, RealTicker, ReplayInfo, ReplayLaunch, ReplayParams, RgsConfig, RgsError, RootStore, Round, RoundInfo, RuntimeConfig, ScriptedRound, SessionStore, SettlePhase, SpinPhase, StakeGame, StakeNetworkManager, StakeNetworkOptions, Ticker, TurboClock, TurboState, UiStore, ViewContext, blockingBeat, createLoader, createNetwork, createStakeGame, currencyFor, defaultPhases, modeCostOf, readRuntime, roundEvents, roundInfo, urlParam };
|
|
218
|
+
export { API_AMOUNT_MULTIPLIER, AudioPort, AudioSpec, AudioValue, AuthenticateRequest, AuthenticateResponse, BOOK_AMOUNT_MULTIPLIER, Balance, BalanceStore, BeatClock, BeatOptions, CreateStakeGameOptions, DEFAULT_TURBO_SPEEDS, EXTRA_DECIMALS, EndRoundRequest, EndRoundResponse, FSM, FeatureItem, GameConfig, GameLoader, GameRound, GameSnapshot, HudPort, IdlePhase, InstantTicker, InterpretBook, JurisdictionConfig, LoaderConfig, LoaderPort, MockNetworkManager, MockOptions, ModeConfig, NetworkManager, Phase, PhaseContext, PlayArgs, PlayRequest, PlayResponse, PresentPhase, RealTicker, ReplayInfo, ReplayLaunch, ReplayParams, RgsConfig, RgsError, RootStore, Round, RoundInfo, RuntimeConfig, SYMBOL_OVERRIDES, ScriptedRound, SessionStore, SettlePhase, SpinPhase, StakeGame, StakeNetworkManager, StakeNetworkOptions, Ticker, TurboClock, TurboState, UiStore, ViewContext, blockingBeat, createLoader, createNetwork, createStakeGame, currencyFor, defaultPhases, isStakeLaunch, modeCostOf, readRuntime, roundEvents, roundInfo, urlParam };
|
|
213
219
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as roundEvents, n as API_AMOUNT_MULTIPLIER, r as BOOK_AMOUNT_MULTIPLIER, t as MockNetworkManager } from "./MockNetworkManager-cj8WT5v0.js";
|
|
2
|
-
import { StakeNetworkManager, createNetwork, readRuntime, urlParam } from "./rgs.js";
|
|
2
|
+
import { StakeNetworkManager, createNetwork, isStakeLaunch, readRuntime, urlParam } from "./rgs.js";
|
|
3
3
|
import { n as RealTicker, t as InstantTicker } from "./ticker-A-XgayZv.js";
|
|
4
4
|
import { BalanceStore, RootStore, SessionStore, UiStore } from "./stores.js";
|
|
5
5
|
import { n as bindMixerToHud, t as bindInputSounds } from "./inputs-X3Tc_HTV.js";
|
|
@@ -262,14 +262,25 @@ const EXTRA_DECIMALS = {
|
|
|
262
262
|
symbol: "IQD"
|
|
263
263
|
}
|
|
264
264
|
};
|
|
265
|
+
/** Symbol-only fixes required by Stake's RGS-communication approval guidelines, layered on
|
|
266
|
+
* top of the @open-slot-ui table (which is otherwise correct on precision + symbol side).
|
|
267
|
+
* Danish Krone must read "KR" — the lib ships lowercase "kr". Taiwan Dollar already resolves
|
|
268
|
+
* to the accepted "NT$", so it needs no override. */
|
|
269
|
+
const SYMBOL_OVERRIDES = { DKK: "KR" };
|
|
265
270
|
/**
|
|
266
|
-
* `resolveCurrency`, but with the missing 3-decimal dinars/rials patched in
|
|
267
|
-
* everywhere a currency code becomes a
|
|
268
|
-
*
|
|
271
|
+
* `resolveCurrency`, but with the missing 3-decimal dinars/rials patched in and the
|
|
272
|
+
* approval-mandated symbol fixes applied. Use this everywhere a currency code becomes a
|
|
273
|
+
* `CurrencySpec` so precision + symbol are correct for every code — `createStakeGame`
|
|
274
|
+
* routes all of its resolution through here.
|
|
269
275
|
*/
|
|
270
276
|
function currencyFor(code) {
|
|
271
|
-
const
|
|
272
|
-
|
|
277
|
+
const up = code ? code.toUpperCase() : "";
|
|
278
|
+
const symbol = up ? SYMBOL_OVERRIDES[up] : void 0;
|
|
279
|
+
const overrides = {
|
|
280
|
+
...up ? EXTRA_DECIMALS[up] : void 0,
|
|
281
|
+
...symbol ? { symbol } : void 0
|
|
282
|
+
};
|
|
283
|
+
return Object.keys(overrides).length ? resolveCurrency(code, overrides) : resolveCurrency(code);
|
|
273
284
|
}
|
|
274
285
|
//#endregion
|
|
275
286
|
//#region src/loader/index.ts
|
|
@@ -533,7 +544,7 @@ function createStakeGame(opts) {
|
|
|
533
544
|
}
|
|
534
545
|
};
|
|
535
546
|
};
|
|
536
|
-
const buildSpec = (s) => ({
|
|
547
|
+
const buildSpec = (s, extra) => ({
|
|
537
548
|
currency: currencyFor(s.currency),
|
|
538
549
|
betLadder: {
|
|
539
550
|
levels: s.betLevels,
|
|
@@ -546,7 +557,8 @@ function createStakeGame(opts) {
|
|
|
546
557
|
},
|
|
547
558
|
controls: {
|
|
548
559
|
fullscreen: { hidden: true },
|
|
549
|
-
...buyFeaturesOf(opts.config).length ? { bonus: { hidden: false } } : {}
|
|
560
|
+
...buyFeaturesOf(opts.config).length ? { bonus: { hidden: false } } : {},
|
|
561
|
+
...extra?.hideBalance ? { balance: { hidden: true } } : {}
|
|
550
562
|
},
|
|
551
563
|
buyFeature: { confirmAboveCost: s.confirmBuyAboveCost },
|
|
552
564
|
...opts.config.rules ? { menu: opts.config.rules } : {},
|
|
@@ -597,7 +609,7 @@ function createStakeGame(opts) {
|
|
|
597
609
|
defaultBet: bet,
|
|
598
610
|
rtp: opts.config.rtp ?? 96,
|
|
599
611
|
confirmBuyAboveCost: STAKE_CONFIRM_ABOVE_COST
|
|
600
|
-
}), await hudOpts());
|
|
612
|
+
}, { hideBalance: true }), await hudOpts());
|
|
601
613
|
stores.session.set({ currency: cur });
|
|
602
614
|
stores.balance.setBalance(0);
|
|
603
615
|
stores.balance.setBet(bet);
|
|
@@ -907,6 +919,6 @@ function buyFeaturesOf(config) {
|
|
|
907
919
|
return out;
|
|
908
920
|
}
|
|
909
921
|
//#endregion
|
|
910
|
-
export { API_AMOUNT_MULTIPLIER, BOOK_AMOUNT_MULTIPLIER, BalanceStore, DEFAULT_TURBO_SPEEDS, EXTRA_DECIMALS, FSM, IdlePhase, InstantTicker, MockNetworkManager, PresentPhase, RealTicker, RootStore, SessionStore, SettlePhase, SpinPhase, StakeNetworkManager, TurboClock, UiStore, blockingBeat, createLoader, createNetwork, createStakeGame, currencyFor, defaultPhases, modeCostOf, readRuntime, roundEvents, roundInfo, urlParam };
|
|
922
|
+
export { API_AMOUNT_MULTIPLIER, BOOK_AMOUNT_MULTIPLIER, BalanceStore, DEFAULT_TURBO_SPEEDS, EXTRA_DECIMALS, FSM, IdlePhase, InstantTicker, MockNetworkManager, PresentPhase, RealTicker, RootStore, SYMBOL_OVERRIDES, SessionStore, SettlePhase, SpinPhase, StakeNetworkManager, TurboClock, UiStore, blockingBeat, createLoader, createNetwork, createStakeGame, currencyFor, defaultPhases, isStakeLaunch, modeCostOf, readRuntime, roundEvents, roundInfo, urlParam };
|
|
911
923
|
|
|
912
924
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/engine/turbo.ts","../src/engine/round.ts","../src/engine/fsm.ts","../src/engine/phases.ts","../src/engine/beat.ts","../src/game/config.ts","../src/currency/index.ts","../src/loader/index.ts","../src/game/createStakeGame.ts"],"sourcesContent":["// Turbo speed + slam-stop, owned by the core. The HUD's turbo control (a 2- or 3-mode cycler)\n// drives a speed multiplier; the game's Present phase awaits `ctx.turbo.delay(ms)` for its\n// spin/anim durations, so turbo shortens them and a slam-stop (`skipRequested`) resolves them\n// instantly — with ZERO game logic. Compliance waits (minimumRoundDuration) use `ctx.ticker`,\n// NOT this, so they're never sped up or skipped.\n\n/** Delay multipliers per turbo level: off (1×) / turbo (0.4×) / super (0.12×). */\nexport const DEFAULT_TURBO_SPEEDS = [1, 0.4, 0.12];\n\nexport interface TurboState {\n /** 0 = off, 1 = turbo, 2 = super — the HUD turbo cycler's index. */\n readonly level: number;\n /** The EFFECTIVE delay multiplier right now — the turbo level, floored by the autoplay\n * speed while autoplay is running (`ctx.turbo.delay` already applies it). */\n readonly speed: number;\n /** True while autoplay/hold is running — game animations are shortened even at turbo off. */\n readonly autoplay: boolean;\n /** True once the player slam-stopped this round (delays resolve instantly). */\n readonly skipped: boolean;\n /** A turbo- + autoplay- + slam-stop-aware delay for GAME animations. NOT for compliance waits. */\n delay(ms: number): Promise<void>;\n}\n\nexport class TurboClock implements TurboState {\n private readonly speeds: number[];\n /** Delay multiplier applied while autoplay/hold runs (the floor for `speed`). Defaults to\n * the turbo (level-1) speed, so autoplay plays at least at turbo pace even at turbo off. */\n private readonly autoplaySpeed: number;\n private _level = 0;\n private _autoplay = false;\n private _skipped = false;\n private readonly pending = new Set<() => void>();\n\n constructor(speeds?: number[], autoplaySpeed?: number) {\n this.speeds = speeds && speeds.length ? speeds : DEFAULT_TURBO_SPEEDS;\n this.autoplaySpeed = autoplaySpeed ?? this.speeds[1] ?? 0.5;\n }\n\n get level(): number {\n return this._level;\n }\n get speed(): number {\n const levelSpeed = this.speeds[this._level] ?? this.speeds[this.speeds.length - 1] ?? 1;\n // Autoplay shortens animations even at turbo off — but never SLOWS a faster turbo level.\n return this._autoplay ? Math.min(levelSpeed, this.autoplaySpeed) : levelSpeed;\n }\n get autoplay(): boolean {\n return this._autoplay;\n }\n get skipped(): boolean {\n return this._skipped;\n }\n\n /** Set the turbo level (clamped to the configured speeds). */\n setLevel(index: number): void {\n const i = Number.isFinite(index) ? Math.trunc(index) : 0;\n this._level = Math.max(0, Math.min(i, this.speeds.length - 1));\n }\n\n /** Mark autoplay/hold as running so `delay()` shortens game animations. The core sets\n * this on autoplayStarted/holdSpinStarted and clears it when they stop. */\n setAutoplay(active: boolean): void {\n this._autoplay = active;\n }\n\n /** Slam-stop: resolve every in-flight delay + make the rest of the round instant. */\n skip(): void {\n this._skipped = true;\n const cbs = [...this.pending];\n this.pending.clear();\n for (const cb of cbs) cb();\n }\n\n /** Clear the slam-stop flag — the core calls this at the start of each spin. */\n resetSkip(): void {\n this._skipped = false;\n }\n\n delay(ms: number): Promise<void> {\n if (this._skipped || ms <= 0) return Promise.resolve();\n return new Promise<void>((resolve) => {\n let done = false;\n const finish = (): void => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n this.pending.delete(finish);\n resolve();\n };\n const timer = setTimeout(finish, ms * this.speed);\n this.pending.add(finish);\n });\n }\n}\n","// The round model the engine drives. The engine derives all MONEY (multiplier, win,\n// stake) from the raw wire round + the bet; the game's `interpretBook` only parses the\n// book EVENTS into its own model (`data`) — the one place a game touches a round.\n\nimport { API_AMOUNT_MULTIPLIER, BOOK_AMOUNT_MULTIPLIER, type Round } from '../rgs/protocol';\n\n/** Money facts of a settled round (all MAJOR units except `multiplier`, a ratio). */\nexport interface RoundInfo {\n mode: string;\n /** Base bet (before the mode's cost multiplier). */\n bet: number;\n /** Mode cost multiplier (× base bet) — the stake charged. */\n cost: number;\n /** The amount staked = bet × cost. */\n stake: number;\n /** Round multiplier relative to the BASE bet (totalWin / bet). */\n multiplier: number;\n /** Total win credited this round (derived: multiplier × bet). */\n totalWin: number;\n /** The server's AUTHORITATIVE win (`raw.payout`, major units); falls back to totalWin\n * when the round carries no explicit payout. Trust this over `totalWin` when they differ. */\n payout: number;\n}\n\n/**\n * A resolved round: money facts + the game's parsed model + the raw wire round. `E` is the\n * game's book-event type — declared once on `createStakeGame`, so `raw` (and `interpretBook`)\n * are TYPED rather than `unknown`.\n */\nexport interface GameRound<T = unknown, E = unknown> extends RoundInfo {\n /** The game's parsed model, from `interpretBook` — driven to the Present phase. */\n data: T;\n /** Whether the raw round still needs `/wallet/end-round` (the engine settles it). */\n active: boolean;\n /** Authoritative post-settlement balance (major units) — applied by the Settle phase. */\n balance: number;\n /** The raw, fully-typed wire round — the game's real server data. */\n raw: Round<E>;\n}\n\n/** The game's ONE money-logic seam: parse the raw round's (typed) events into your model. Pure. */\nexport type InterpretBook<T, E = unknown> = (raw: Round<E>, info: RoundInfo) => T;\n\n/** Build the money facts from a raw wire round + the base bet + the mode cost. */\nexport function roundInfo<E = unknown>(raw: Round<E>, bet: number, cost: number): RoundInfo {\n // Stake wire convention: `payoutMultiplier` is a plain Payout/Amount ratio (1× → 1)\n // in BOOK units (×100). totalWin is relative to the BASE bet; `payout` is the server's\n // own win amount (API units → major) when the round carries one.\n const multiplier = raw.payoutMultiplier / BOOK_AMOUNT_MULTIPLIER;\n const totalWin = multiplier * bet;\n const payout = raw.payout != null ? raw.payout / API_AMOUNT_MULTIPLIER : totalWin;\n return { mode: raw.mode, bet, cost, stake: bet * cost, multiplier, totalWin, payout };\n}\n","// The round state machine. A round is an explicit sequence of awaited phases\n// (Idle → Spin → Present → Settle); each `enter` does its work + transitions on. The\n// core ships Idle/Spin/Settle; the game writes Present (and may override any). The FSM\n// + context are pure — the HUD/view/audio arrive via ports, so a whole round runs\n// headless (see the InstantTicker + MockNetworkManager).\n\nimport type { NetworkManager } from '../rgs/network';\nimport type { RootStore } from '../stores/index';\nimport type { Ticker } from './ticker';\nimport type { TurboState } from './turbo';\nimport type { HudPort } from './hud-port';\nimport type { GameRound, InterpretBook } from './round';\nimport type { GameConfig } from '../game/config';\n\n/** A fixed value or a per-voice random spread (`{ base, jitter }`) — structurally matches\n * zvuk's `VoiceJitter` without importing it, so the engine stays zvuk-free. */\nexport type AudioValue = number | { base?: number; jitter?: number };\n\n/** The minimal boot-loader surface a game's phases touch (satisfied by the core's\n * `GameLoader`) — kept structural so the engine needn't import the loader module. */\nexport interface LoaderPort {\n setProgress(p: number): void;\n done(): Promise<void>;\n}\n\n/** The minimal audio surface a game's phases call (satisfied by `@stakeplate/core/audio`). */\nexport interface AudioPort {\n /** Fire a one-shot. `volume`/`pitch` accept jitter so repeated cues vary per voice. */\n play(name: string, opts?: { bus?: string; volume?: AudioValue; pitch?: AudioValue }): void;\n music(name: string, opts?: { fadeIn?: number }): void;\n stopMusic(opts?: { fade?: number }): void;\n}\n\n/** Handed to every phase. `round` is set by SpinPhase and read by Present/Settle. `E` is\n * the game's book-event type, so `interpretBook`/`round.raw` are typed. */\nexport interface PhaseContext<T = unknown, V = unknown, E = unknown> {\n readonly config: GameConfig;\n readonly stores: RootStore;\n readonly network: NetworkManager;\n readonly hud: HudPort;\n readonly ticker: Ticker;\n /** The game's mounted view (scene/presenter/stores) — whatever `mountView` returned. */\n readonly view: V;\n readonly audio: AudioPort | null;\n /** Turbo speed + slam-stop (core-owned). Use `ctx.turbo.delay(ms)` for spin/anim timing. */\n readonly turbo: TurboState;\n /** The boot loader, if one was configured (else `null`) — drive progress / `done()`. */\n readonly loader: LoaderPort | null;\n readonly interpretBook: InterpretBook<T, E>;\n readonly fsm: FSM<T, V, E>;\n round: GameRound<T, E> | null;\n /** Cost multiplier for a mode key (from `config.modes`, default 1). */\n modeCost(mode: string): number;\n}\n\nexport interface Phase<T = unknown, V = unknown, E = unknown> {\n readonly name: string;\n enter(ctx: PhaseContext<T, V, E>): Promise<void> | void;\n exit?(ctx: PhaseContext<T, V, E>): void;\n}\n\nexport class FSM<T = unknown, V = unknown, E = unknown> {\n private ctx: PhaseContext<T, V, E> | null = null;\n private readonly byName = new Map<string, Phase<T, V, E>>();\n private _current = '';\n\n /** Later phases with the same `name` win — so a game's Present overrides the default. */\n constructor(phases: Phase<T, V, E>[]) {\n for (const p of phases) this.byName.set(p.name, p);\n }\n\n bind(ctx: PhaseContext<T, V, E>): void {\n this.ctx = ctx;\n }\n\n get current(): string {\n return this._current;\n }\n\n has(name: string): boolean {\n return this.byName.has(name);\n }\n\n async transition(name: string): Promise<void> {\n if (!this.ctx) throw new Error('[FSM] not bound to a context');\n const next = this.byName.get(name);\n if (!next) throw new Error(`[FSM] unknown phase: ${name}`);\n this.byName.get(this._current)?.exit?.(this.ctx);\n this._current = name;\n await next.enter(this.ctx);\n }\n}\n","// The default round phases (Idle → Spin → Present → Settle). The game writes Present\n// (animate the round) and may override any of these. Balance/bet flow through the\n// stores (createStakeGame reacts store→HUD); the phases call the HUD directly only for\n// reportRound + errors.\n\nimport { API_AMOUNT_MULTIPLIER, type Round } from '../rgs/protocol';\nimport { roundInfo } from './round';\nimport type { Phase, PhaseContext } from './fsm';\n\n/** Wait for the next spin (the HUD's `spinRequested` drives the transition to Spin). */\nexport class IdlePhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'idle';\n enter(ctx: PhaseContext<T, V, E>): void {\n ctx.stores.ui.setSpinning(false);\n }\n}\n\n/** Pick the mode, take the stake, ask the RGS, parse the round, hand off to Present. */\nexport class SpinPhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'spin';\n async enter(ctx: PhaseContext<T, V, E>): Promise<void> {\n const { stores, network, hud } = ctx;\n const mode = stores.ui.nextMode();\n const cost = ctx.modeCost(mode);\n const bet = stores.balance.bet;\n const stake = bet * cost;\n\n stores.ui.setSpinning(true);\n stores.balance.debitStake(stake); // stake leaves now; the win lands at Settle\n\n try {\n const play = await network.play({ bet, mode });\n // The transport is game-agnostic (events: unknown); the game declared `E`, so we\n // narrow here — the game owns the shape it parses in `interpretBook`.\n const raw = play.round as Round<E>;\n const info = roundInfo(raw, bet, cost);\n const data = ctx.interpretBook(raw, info);\n // DO NOT /wallet/end-round here. An ACTIVE round is left OPEN so a refresh DURING\n // Present (the game's reel/scene animation) can recover it — SettlePhase settles it\n // once the round has finished playing out. For an active round `play.balance` is\n // post-DEBIT (stake gone, win not yet credited), so the post-win balance the player\n // sees at Settle is play.balance + the (authoritative-from-book) win; SettlePhase then\n // reconciles to the server's own settled figure. A round that is already settled (a\n // loss, or an immediate-settle mock) reports its FINAL balance in play.balance — use\n // it verbatim, or the win would be double-counted.\n const settledMoney = play.balance.amount / API_AMOUNT_MULTIPLIER;\n ctx.round = {\n ...info,\n data,\n active: raw.active ?? false,\n balance: raw.active ? settledMoney + info.totalWin : settledMoney,\n raw,\n };\n } catch (err) {\n stores.balance.refund(stake); // the round never happened\n stores.ui.setSpinning(false);\n const msg = err instanceof Error ? err.message : String(err);\n const code = msg.match(/ERR_[A-Z_]+/)?.[0];\n if (code) hud.showRgsError(code);\n else hud.showError(msg);\n await ctx.fsm.transition('idle');\n return;\n }\n\n await ctx.fsm.transition('present');\n }\n}\n\n/** DEFAULT Present — nothing to animate. The game overrides `present` to play its scene. */\nexport class PresentPhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'present';\n async enter(ctx: PhaseContext<T, V, E>): Promise<void> {\n await ctx.fsm.transition('settle');\n }\n}\n\n/** Apply the authoritative balance + win, settle the round for the HUD, then Idle. */\nexport class SettlePhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'settle';\n private roundStartedAt = 0;\n\n async enter(ctx: PhaseContext<T, V, E>): Promise<void> {\n const r = ctx.round;\n if (r) {\n ctx.stores.balance.settle(r.balance, r.totalWin);\n ctx.hud.reportRound(r.totalWin, r.bet);\n // minimumRoundDuration: pad to the jurisdiction minimum (best-effort; the boot\n // records the round start, so this covers the whole spin→settle span).\n const minMs = ctx.stores.session.jurisdiction.minimumRoundDuration ?? 0;\n const elapsed = (ctx.ticker.now() - this.roundStartedAt) * 1000;\n if (minMs > 0 && elapsed < minMs) await ctx.ticker.delay(minMs - elapsed);\n // The round has now finished playing out visually (Present + the min-duration pad).\n // ONLY NOW settle it on the server — SpinPhase deliberately left it OPEN so a refresh\n // mid-Present recovers the round instead of losing it. The win already shows (from the\n // book); this reconciles to the server's authoritative post-settlement balance.\n if (r.active) {\n try {\n const end = await ctx.network.endRound();\n ctx.stores.balance.setBalance(end.balance.amount / API_AMOUNT_MULTIPLIER);\n } catch (err) {\n // The win is already credited locally; a settle hiccup self-heals on the next\n // authenticate (the RGS reports the still-open round). Surface it, don't crash.\n console.warn('[settle] end-round failed; balance reconciles on next authenticate:', err);\n }\n }\n }\n ctx.stores.ui.setSpinning(false);\n await ctx.fsm.transition('idle');\n }\n\n /** Called by SpinPhase-adjacent boot wiring to timestamp the round start. */\n markStart(now: number): void {\n this.roundStartedAt = now;\n }\n}\n\n/** The default phase set (game phases are appended after → same-name overrides win). */\nexport function defaultPhases<T = unknown, V = unknown, E = unknown>(): Phase<T, V, E>[] {\n return [new IdlePhase<T, V, E>(), new SpinPhase<T, V, E>(), new PresentPhase<T, V, E>(), new SettlePhase<T, V, E>()];\n}\n","// A blocking \"announcement beat\" — the pattern behind moments like a bonus trigger or a\n// \"… ELIMINATED!\" banner: pause so the reveal settles, SHOW the announcement, HOLD it\n// alone on screen, HIDE it, then a trailing pause before play resumes. The point is that\n// the round never plays UNDER the announcement — everything waits for the beat.\n//\n// The game owns the visuals (`show`/`hide` — mount a banner, toggle a class, play an sfx);\n// the core owns the timing + sequencing so every game's beats feel consistent.\n\nimport type { Ticker } from './ticker';\nimport type { TurboState } from './turbo';\n\nexport interface BeatOptions {\n /** Pause before the announcement shows — lets the reveal settle. Default 0. */\n leadMs?: number;\n /** How long the announcement holds alone on screen. */\n holdMs: number;\n /** Pause after hiding, before play resumes. Default 0. */\n trailMs?: number;\n /** Reveal the announcement (banner/sfx). Called after the lead pause. */\n show: () => void;\n /** Hide it. Called after the hold, before the trailing pause. Optional (some banners\n * self-dismiss). */\n hide?: () => void;\n /**\n * Timing source. `'real'` (default) is a fixed, unskippable beat via the ticker — an\n * announcement is a deliberate moment that a slam-stop shouldn't blow past. `'turbo'`\n * makes the pauses turbo-/autoplay-/slam-stop-aware (shorter under turbo, skippable).\n */\n timing?: 'real' | 'turbo';\n}\n\n/** Timing surface the beat needs — a `PhaseContext` satisfies it (`ctx.ticker`/`ctx.turbo`). */\nexport interface BeatClock {\n ticker: Ticker;\n turbo: TurboState;\n}\n\n/**\n * Run a blocking announcement beat: lead → show → hold → hide → trail. Awaitable, so a\n * Present phase just `await blockingBeat(ctx, { … })` and the round pauses for the whole\n * thing. Returns when the beat (including the trailing pause) is over.\n */\nexport async function blockingBeat(ctx: BeatClock, opts: BeatOptions): Promise<void> {\n const wait = opts.timing === 'turbo' ? (ms: number) => ctx.turbo.delay(ms) : (ms: number) => ctx.ticker.delay(ms);\n if (opts.leadMs && opts.leadMs > 0) await wait(opts.leadMs);\n opts.show();\n await wait(opts.holdMs);\n opts.hide?.();\n if (opts.trailMs && opts.trailMs > 0) await wait(opts.trailMs);\n}\n","// The per-game configuration passed to `createStakeGame`. Everything the core needs to\n// stand up the HUD + drive the round; the game's mechanics live in its scene + phases.\n\nexport interface ModeConfig {\n /** Cost multiplier (× base bet). `base` is 1. */\n cost: number;\n /** A one-shot bought bonus — shown as a `Buy` card in the buy-feature modal (opened by the\n * bonus button). Buying it spins this mode once (through the jurisdiction confirm gate). */\n buy?: boolean;\n /** An activatable per-spin bet surcharge — shown as an `Activate` card (vs a one-tap `Buy`). */\n boost?: boolean;\n /** Display name for the feature card (defaults to the capitalized mode key). */\n name?: string;\n /** Card art (URL or data URI) for the feature card. A neutral gradient is used when absent. */\n image?: string;\n}\n\nexport interface GameConfig {\n title: string;\n version?: string;\n /** Fallback currency code; the session/`?currency=` wins. */\n currency?: string;\n /**\n * Theoretical RTP percentage — DISPLAY ONLY (the RTP readout + rules). NOT authoritative:\n * the server (`auth.config.rtp`) wins, and the certified math report is the source of\n * truth. This is only a last-resort fallback; prefer never hand-typing it (drift risk).\n */\n rtp?: number;\n /** Mode key → cost multiplier (a number) or a `ModeConfig`. `base` defaults to 1. */\n modes?: Record<string, ModeConfig | number>;\n /**\n * Minimum selectable bet, in MAJOR units of the account currency — an EXPLICIT client\n * floor. Any server bet-ladder level below this is dropped from the HUD ladder, so the\n * smallest possible win (bet × the game's minimum payout) can never round below one\n * minimal currency unit. Set it per game to your smallest legal stake — e.g. `0.05` when\n * the minimum payout is ×0.2 (0.05 × 0.2 = 0.01 = one cent). `undefined` → derive it from\n * the currency (5 minimal units — a safe default for a ×0.2 minimum payout). The server\n * ladder stays authoritative for the LEVELS offered; this only trims the low end + snaps\n * the default up to the first legal level.\n */\n minBet?: number;\n /** Rules/info menu for the HUD (`@open-slot-ui` `MenuSpec`) — build it with `@stakeplate/core/rules` `buildRules`. */\n rules?: unknown;\n /** i18n messages per locale (`{ en: { key: text }, es: {…} }`) for the HUD + rules text. */\n messages?: Record<string, Record<string, string>>;\n /** SOCIAL/sweepstakes wording per locale — swapped in when social mode is on. Merge in\n * `buildRules().socialEn` so the core's disclaimer/guide are social-safe. */\n socialMessages?: Record<string, Record<string, string>>;\n /** Delay multipliers per turbo level (off / turbo / super). Default `[1, 0.4, 0.12]`. */\n turboSpeeds?: number[];\n /** Pause (ms) between autoplay/hold spins. Default 250. Scales with turbo. */\n autoplayGapMs?: number;\n /** Extra `@open-slot-ui` `UISpec` fields merged into the built spec (escape hatch). */\n spec?: Record<string, unknown>;\n}\n\n// NOTE (server-authoritative): the bet ladder (`betLevels` + `defaultBetLevel`) and the\n// buy-feature confirm threshold are NOT game config — the RGS `authenticate` response owns\n// the ladder (per currency/jurisdiction) and the jurisdiction owns the confirm policy. The\n// core reads them from `auth.config`; a game only declares that a mode is a buy (`modes`).\n\n/** The cost multiplier for a mode key (default 1 for `base` / unknown modes). */\nexport function modeCostOf(config: GameConfig, mode: string): number {\n const m = config.modes?.[mode];\n if (typeof m === 'number') return m;\n if (m && typeof m === 'object') return m.cost;\n return 1;\n}\n","// @stakeplate/core/currency — currency resolution with the 3-decimal fiat the\n// @open-slot-ui table doesn't (yet) carry patched in.\n//\n// The lib's `resolveCurrency` knows the common codes (USD 2dp, JPY 0dp, BTC 8dp, the\n// Kuwaiti/Bahraini/Jordanian dinars KWD/BHD/JOD 3dp, the Stake social coins, …). For a\n// code it DOESN'T know it falls back to 2 decimals — which silently truncates the sub-unit\n// of the other three-decimal currencies. That matters: e.g. the Omani Rial's minimal\n// stake 0.01 at the lowest ×0.2 coefficient is a 0.002 win, which renders as \"0.00\" at\n// two decimals. `currencyFor` passes the correct precision (+ a sensible symbol) through\n// `resolveCurrency`'s overrides so the full digits show end-to-end (bet, balance, net,\n// win plaque, buy modal, replay).\n\nimport { resolveCurrency, type CurrencySpec } from '@open-slot-ui/core';\n\n/** Three-decimal fiat missing from the @open-slot-ui currency table — the Gulf/Arab\n * dinars & rials (the lib already covers KWD/BHD/JOD). `symbol` follows the lib's short\n * latinised convention for the region (KWD→\"KD\", …). */\nexport const EXTRA_DECIMALS: Readonly<Record<string, { decimals: number; symbol: string }>> = {\n OMR: { decimals: 3, symbol: 'OMR' }, // Omani Rial\n TND: { decimals: 3, symbol: 'DT' }, // Tunisian Dinar\n LYD: { decimals: 3, symbol: 'LD' }, // Libyan Dinar\n IQD: { decimals: 3, symbol: 'IQD' }, // Iraqi Dinar\n};\n\n/**\n * `resolveCurrency`, but with the missing 3-decimal dinars/rials patched in. Use this\n * everywhere a currency code becomes a `CurrencySpec` so precision is correct for every\n * code — `createStakeGame` routes all of its resolution through here.\n */\nexport function currencyFor(code: string): CurrencySpec {\n const extra = code ? EXTRA_DECIMALS[code.toUpperCase()] : undefined;\n return extra ? resolveCurrency(code, extra) : resolveCurrency(code);\n}\n","// The boot experience — a single overlay that carries the player from LOADING straight into\n// an optional FEATURES splash and then out of the way, all in one continuous element (no\n// flash between screens). `createStakeGame({ loader })` shows it the instant boot starts:\n//\n// LOADING — logo (pulsing) + a progress bar the core advances across the boot milestones\n// (init → auth → HUD → view), over an optional blurred cover image + vignette.\n// FEATURES — (if `features` are configured) once boot is ready the SAME logo glides up to\n// the top, the bar fades out, and 2–3 feature tiles (image + short caption, all\n// scaled to one size) fade + scale in with a \"press to continue\" prompt.\n// REVEAL — a tap / key press (or auto, with no features) fades the overlay away to the\n// game, which is already booted + idle behind it.\n//\n// So the game gets a `loader → features → idle` flow for free — it only writes its `present`\n// phase. Pure DOM + CSS: it paints before pixi/zvuk load and removes itself when done.\n\nlet seq = 0;\n\n/** One feature card on the intro splash — an image with a short (may be multi-line) caption. */\nexport interface FeatureItem {\n /** Image URL / data-URI. Rendered at a uniform height so mismatched art still lines up. */\n image: string;\n /** Short caption under the image. `\\n` for line breaks. */\n text: string;\n}\n\nexport interface LoaderConfig {\n /** Big title under the logo (LOADING phase only). Defaults to the game title. */\n title?: string;\n /** Small line under the title (LOADING phase). */\n subtitle?: string;\n /** URL/data-URI of the logo. Pulses while loading; glides to the top for the features\n * splash. Omit for a CSS ring spinner (and no move-up). */\n logo?: string;\n /** Solid colour behind everything. Default `#0b0d12`. */\n background?: string;\n /** Optional cover image (ideally the game's own backdrop, for a seamless reveal). */\n backgroundImage?: string;\n /** Blur the cover image. Default `true`. */\n blur?: boolean;\n /** Accent colour — progress fill + title glow + caption text. Default `#d99000`. */\n accent?: string;\n /** Minimum time the LOADING phase stays up, so the bar always fills. Default `1200`ms. */\n minDurationMs?: number;\n /** Stacking order. Default a very high value so it sits over the game. */\n zIndex?: number;\n /**\n * If `true`, the core shows the loader + advances progress but does NOT auto-advance past\n * LOADING — the game calls `ctx.loader.done()` itself (e.g. after its scene finishes loading\n * art). Default `false`. (The FEATURES splash always waits for the player regardless.)\n */\n manual?: boolean;\n /** 2–3 feature cards → show a FEATURES splash after loading. Omit for a plain loader. */\n features?: FeatureItem[];\n /** The dismiss prompt under the feature cards. Default `PRESS TO CONTINUE`. */\n continueText?: string;\n}\n\nexport interface GameLoader {\n /** The overlay element (already in the document). */\n readonly el: HTMLElement;\n /** Drive the progress bar. Clamped to the range it has already reached (never goes back). */\n setProgress(p: number): void;\n /** Advance out of LOADING: fill to 100%, honour `minDurationMs`, then either show the\n * FEATURES splash (waiting for the player to continue) or — with no features — pop away.\n * Resolves once the overlay is fully gone. Idempotent (first call wins). */\n done(): Promise<void>;\n /** Remove immediately without any outro (used on a boot error). */\n remove(): void;\n}\n\nconst isBrowser = (): boolean => typeof document !== 'undefined' && !!document.body;\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/** Create + mount the boot overlay. Safe to call before anything else in boot. */\nexport function createLoader(config: LoaderConfig = {}): GameLoader {\n const startedAt = Date.now();\n const minMs = config.minDurationMs ?? 1200;\n const accent = config.accent ?? '#d99000';\n const bg = config.background ?? '#0b0d12';\n const z = config.zIndex ?? 2147483000;\n const blur = config.blur ?? true;\n const features = config.features ?? [];\n const hasLogo = !!config.logo;\n let progress = 0;\n let donePromise: Promise<void> | null = null;\n\n // Headless / SSR guard — a no-op handle so callers needn't branch.\n if (!isBrowser()) {\n return { el: null as unknown as HTMLElement, setProgress() {}, done: () => Promise.resolve(), remove() {} };\n }\n\n const id = `sp-loader-${++seq}`;\n const root = document.createElement('div');\n root.id = id;\n root.className = 'sp-loader phase-loading';\n\n const style = document.createElement('style');\n style.textContent = `\n #${id}{position:fixed;inset:0;z-index:${z};background:${config.backgroundImage ? 'transparent' : bg};overflow:hidden;\n font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;\n opacity:1;transition:opacity .5s ease}\n #${id}.sp-done{opacity:0}\n #${id} .sp-bgimg{position:absolute;inset:-8%;background-position:center;background-size:cover;\n ${blur ? 'filter:blur(6px);' : ''}transform:scale(1.06)}\n #${id} .sp-vign{position:absolute;inset:0;background:radial-gradient(120% 90% at 50% 42%,transparent 40%,rgba(0,0,0,.62) 100%)}\n /* Logo — same element in both phases; glides + shrinks from centre to the top. */\n #${id} .sp-logo{position:absolute;left:50%;top:36%;transform:translate(-50%,-50%);\n width:min(42vw,240px);height:auto;filter:drop-shadow(0 14px 24px rgba(0,0,0,.55));\n animation:sp-pulse 1.5s ease-in-out infinite;\n transition:top .6s cubic-bezier(.5,0,.2,1),width .6s cubic-bezier(.5,0,.2,1)}\n #${id}.phase-features .sp-logo{top:15%;width:min(30vw,180px);animation:none}\n /* LOADING group (title/subtitle + bar) — fades out on the way to features. */\n #${id} .sp-load{position:absolute;left:50%;top:58%;transform:translate(-50%,-50%);\n display:flex;flex-direction:column;align-items:center;gap:14px;\n transition:opacity .35s ease}\n #${id}.phase-features .sp-load{opacity:0;pointer-events:none}\n #${id} .sp-spin{width:60px;height:60px;border-radius:50%;border:5px solid rgba(255,255,255,.18);\n border-top-color:${accent};animation:sp-rot .8s linear infinite}\n #${id} .sp-title{margin:0;color:#fff;font-weight:800;letter-spacing:.02em;\n font-size:clamp(20px,3.6vw,36px);text-align:center;text-shadow:0 3px 10px rgba(0,0,0,.55),0 0 22px ${accent}55}\n #${id} .sp-sub{margin:0;color:#ffffffcc;font-size:clamp(12px,2vw,16px);text-align:center;text-shadow:0 2px 6px rgba(0,0,0,.5)}\n #${id} .sp-bar{position:relative;width:min(60vw,260px);height:8px;border-radius:99px;background:rgba(255,255,255,.16);overflow:hidden}\n #${id} .sp-fill{position:absolute;left:0;top:0;bottom:0;width:0%;border-radius:99px;background:${accent};transition:width .35s ease}\n /* FEATURES splash — tiles + prompt; scales/fades in once boot is ready. */\n #${id} .sp-feat{position:absolute;left:0;right:0;top:30%;bottom:0;display:flex;flex-direction:column;\n align-items:center;justify-content:flex-start;gap:5vh;padding:0 4vw;\n opacity:0;transform:scale(.94);pointer-events:none;transition:opacity .5s ease,transform .5s ease}\n #${id}.phase-features .sp-feat{opacity:1;transform:scale(1);pointer-events:auto}\n #${id} .sp-tiles{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;\n gap:clamp(18px,5vw,64px);max-width:94vw}\n #${id} .sp-tile{display:flex;flex-direction:column;align-items:center;gap:14px;\n width:clamp(110px,22vw,210px);opacity:0;transform:translateY(16px) scale(.9);\n transition:opacity .45s ease,transform .45s cubic-bezier(.34,1.56,.64,1)}\n #${id}.phase-features .sp-tile{opacity:1;transform:none}\n #${id} .sp-tile img{height:clamp(66px,13vh,150px);width:auto;max-width:100%;object-fit:contain;\n filter:drop-shadow(0 8px 14px rgba(0,0,0,.45))}\n #${id} .sp-tile span{color:${accent};font-weight:800;text-transform:uppercase;letter-spacing:.02em;\n text-align:center;white-space:pre-line;line-height:1.15;font-size:clamp(13px,2.1vw,21px);\n text-shadow:0 2px 6px rgba(0,0,0,.6)}\n #${id} .sp-cont{margin-top:auto;margin-bottom:5vh;color:#ffffffcc;text-transform:uppercase;\n letter-spacing:.16em;font-size:clamp(11px,1.6vw,15px);animation:sp-blink 1.7s ease-in-out infinite}\n @keyframes sp-pulse{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-50%) scale(1.06)}}\n @keyframes sp-rot{to{transform:rotate(360deg)}}\n @keyframes sp-blink{0%,100%{opacity:.4}50%{opacity:1}}\n @media (prefers-reduced-motion:reduce){#${id} .sp-logo,#${id} .sp-spin,#${id} .sp-cont{animation:none}}\n `;\n\n if (config.backgroundImage) {\n const img = document.createElement('div');\n img.className = 'sp-bgimg';\n img.style.backgroundImage = `url(${config.backgroundImage})`;\n root.appendChild(img);\n const vign = document.createElement('div');\n vign.className = 'sp-vign';\n root.appendChild(vign);\n }\n\n if (hasLogo) {\n const logo = document.createElement('img');\n logo.className = 'sp-logo';\n logo.src = config.logo!;\n logo.alt = '';\n root.appendChild(logo);\n }\n\n // LOADING group.\n const load = document.createElement('div');\n load.className = 'sp-load';\n if (!hasLogo) {\n const spin = document.createElement('div');\n spin.className = 'sp-spin';\n load.appendChild(spin);\n }\n if (config.title) {\n const h = document.createElement('h1');\n h.className = 'sp-title';\n h.textContent = config.title;\n load.appendChild(h);\n }\n if (config.subtitle) {\n const p = document.createElement('p');\n p.className = 'sp-sub';\n p.textContent = config.subtitle;\n load.appendChild(p);\n }\n const bar = document.createElement('div');\n bar.className = 'sp-bar';\n const fill = document.createElement('div');\n fill.className = 'sp-fill';\n bar.appendChild(fill);\n load.appendChild(bar);\n root.appendChild(load);\n\n // FEATURES group (built only when configured).\n if (features.length) {\n const feat = document.createElement('div');\n feat.className = 'sp-feat';\n const tiles = document.createElement('div');\n tiles.className = 'sp-tiles';\n features.forEach((f, i) => {\n const tile = document.createElement('div');\n tile.className = 'sp-tile';\n tile.style.transitionDelay = `${0.12 + i * 0.12}s`; // staggered entrance\n const img = document.createElement('img');\n img.src = f.image;\n img.alt = '';\n const cap = document.createElement('span');\n cap.textContent = f.text;\n tile.append(img, cap);\n tiles.appendChild(tile);\n });\n const cont = document.createElement('div');\n cont.className = 'sp-cont';\n cont.textContent = config.continueText ?? 'PRESS TO CONTINUE';\n feat.append(tiles, cont);\n root.appendChild(feat);\n }\n\n root.appendChild(style);\n document.body.appendChild(root);\n\n const setProgress = (p: number): void => {\n const next = Math.max(progress, Math.min(1, Number.isFinite(p) ? p : 0));\n progress = next;\n fill.style.width = `${Math.round(next * 100)}%`;\n };\n\n const remove = (): void => {\n root.remove();\n style.remove();\n // Clean up the build-time boot backdrop injected by the `stakeplateBoot` Vite plugin\n // (if present) — the game is now covering it.\n document.getElementById('sp-bootbg')?.remove();\n document.getElementById('sp-boot-style')?.remove();\n };\n\n // Transition LOADING → FEATURES, then resolve once the player dismisses it.\n const runFeatures = (): Promise<void> =>\n new Promise((resolve) => {\n root.classList.remove('phase-loading');\n root.classList.add('phase-features');\n const dismiss = (): void => {\n root.removeEventListener('pointerdown', dismiss);\n window.removeEventListener('keydown', onKey);\n resolve();\n };\n const onKey = (e: KeyboardEvent): void => { if (e.key === ' ' || e.key === 'Enter') dismiss(); };\n // Arm dismissal only after the splash has animated in (so a stray click can't skip it).\n setTimeout(() => {\n root.addEventListener('pointerdown', dismiss);\n window.addEventListener('keydown', onKey);\n }, 500);\n });\n\n const done = (): Promise<void> => {\n if (donePromise) return donePromise;\n donePromise = (async () => {\n setProgress(1);\n const wait = minMs - (Date.now() - startedAt);\n if (wait > 0) await sleep(wait);\n if (features.length) await runFeatures(); // hold on the splash until the player continues\n root.classList.add('sp-done'); // fade the whole overlay out\n await sleep(520);\n remove();\n })();\n return donePromise;\n };\n\n return { el: root, setProgress, done, remove };\n}\n","// createStakeGame — the one-call façade. `start()` runs the whole compliant boot with\n// zero game involvement: runtime → network → HUD → (replay | authenticate → configure\n// HUD → resume active round → wire events) → run the FSM. Connection/auth failure →\n// the themed, blocking showBootError. The game supplies only config + interpretBook +\n// mountView (+ optional Present phase, audio). This is the ONLY module that imports the\n// HUD/pixi peers; the engine stays decoupled behind ports.\n\nimport { Application } from 'pixi.js';\nimport { mountHud, showBootError, mountBuyFeatureModal, type BootedHud, type FeatureSpec } from '@open-slot-ui/pixi';\nimport { loadBuiltinArt } from '@open-slot-ui/pixi/art';\nimport { isSocialCurrency } from '@open-slot-ui/core';\nimport { currencyFor } from '../currency';\nimport { reaction } from 'mobx';\nimport { readRuntime, type RuntimeConfig } from '../rgs/runtime';\nimport { createNetwork, type NetworkManager } from '../rgs/network';\nimport { API_AMOUNT_MULTIPLIER, type Round } from '../rgs/protocol';\nimport { RootStore } from '../stores/index';\nimport { RealTicker, type Ticker } from '../engine/ticker';\nimport { TurboClock, type TurboState } from '../engine/turbo';\nimport { FSM, type AudioPort, type Phase, type PhaseContext } from '../engine/fsm';\nimport { defaultPhases } from '../engine/phases';\nimport { roundInfo, type InterpretBook } from '../engine/round';\nimport { bindMixerToHud, type MixerLike } from '../audio/bind';\nimport { bindInputSounds, type InputSoundMap } from '../audio/inputs';\nimport type { GameAudioOptions, SoundEntry } from '../audio';\nimport { createLoader, type GameLoader, type LoaderConfig } from '../loader';\nimport { modeCostOf, type GameConfig } from './config';\n\n/**\n * Declarative audio: hand the core your sounds and it lazily creates the `@schmooky/zvuk`\n * mixer (master → music/sfx/ambience buses + ducking), preloads them, and auto-binds it to\n * the HUD (sliders/mute + unlock). zvuk loads in its OWN async chunk — a game without sound\n * pays nothing. (Advanced: pass a ready `GameAudio` instance instead, for custom buses/FX.)\n */\nexport interface AudioSpec extends GameAudioOptions {\n sounds: SoundEntry[];\n /** Optional: play a cue on HUD input events (spin/bet/autoplay/turbo/toggle/skip). The\n * core auto-binds these after the HUD mounts — the named sounds must be in `sounds`. */\n inputSounds?: InputSoundMap;\n}\n\n/** Passed to `mountView` — everything the game's scene needs (not the round/fsm yet). */\nexport interface ViewContext {\n config: GameConfig;\n stores: RootStore;\n hud: BootedHud;\n ticker: Ticker;\n audio: AudioPort | null;\n /** Turbo speed + slam-stop (core-owned) — the scene may branch on `turbo.level`/`speed`. */\n turbo: TurboState;\n /** The boot loader (if `loader` was configured) — drive `setProgress` while your scene\n * loads art, and (with `loader.manual`) call `done()` when the scene is ready. `null`\n * when no loader is used. */\n loader: GameLoader | null;\n}\n\nexport interface CreateStakeGameOptions<T = unknown, V = unknown, E = unknown> {\n config: GameConfig;\n /** The game's ONE money seam: typed RGS round (`Round<E>`) → your model. Pure. */\n interpretBook: InterpretBook<T, E>;\n /** Mount the game's pixi scene/presenter/stores; returns the view handed to phases. */\n mountView: (host: HTMLElement, ctx: ViewContext) => V;\n /** The game's Present phase (+ any overrides); Idle/Spin/Settle are provided. */\n phases?: Phase<T, V, E>[];\n /** Sounds (declarative — the core builds + wires the mixer) OR a ready `GameAudio`/AudioPort. */\n audio?: AudioPort | AudioSpec | null;\n /** Override the transport (tests / a supplied mock). */\n network?: NetworkManager;\n /** Override launch params (tests / embedding). */\n runtime?: Partial<RuntimeConfig>;\n /** Host for the pixi HUD canvas (default document.body). */\n hudHost?: HTMLElement;\n /** Host for the game scene (default = hudHost). */\n sceneHost?: HTMLElement;\n /** Passthrough to `mountHud` (spinSkin, icons, gsap, menu:false, hooks, …). */\n hudOptions?: Record<string, unknown>;\n /**\n * A configurable boot loader — shows the instant boot starts, advances across the boot\n * milestones, then fills + pops away when the game is ready. Pass a {@link LoaderConfig}\n * to enable it (title defaults to `config.title`); omit or `false` to render none (the\n * game keeps its own HTML loader). With `manual: true`, call `ctx.loader.done()` yourself\n * (e.g. after the scene's art finishes loading).\n */\n loader?: LoaderConfig | false;\n}\n\n/**\n * Stake policy: buys/activations costing more than this many × base bet require a confirm\n * (no one-click). A buy-feature always costs far more than 2×, so in practice this means\n * \"always confirm a buy\". The jurisdiction (`auth.config.jurisdiction`) may override it; a\n * game never sets it — it's compliance, not design.\n */\nconst STAKE_CONFIRM_ABOVE_COST = 2;\n\n/** Fallback min-payout coefficient used ONLY when `config.minBet` is unset: the\n * currency-derived floor is `minUnit / MIN_PAYOUT_COEF` (= 5 minimal units at 0.2). */\nconst MIN_PAYOUT_COEF = 0.2;\n\n/** Persisted player UI preferences (sound mute + turbo mode). Best-effort — every access\n * is guarded: Safari Private Browsing, a cross-origin-iframe storage block, or a hardened\n * session throws on access, so this degrades to \"no persistence\", never into the boot. */\nconst PREFS_KEY = 'stakeplate.prefs';\ninterface GamePrefs {\n muted?: boolean;\n turbo?: number;\n}\nfunction readPrefs(): GamePrefs {\n try {\n return JSON.parse(localStorage.getItem(PREFS_KEY) || '{}') as GamePrefs;\n } catch {\n return {};\n }\n}\nfunction writePrefs(patch: GamePrefs): void {\n try {\n localStorage.setItem(PREFS_KEY, JSON.stringify({ ...readPrefs(), ...patch }));\n } catch {\n /* storage disabled / full — preferences just won't persist this session */\n }\n}\n\n/** Drop server bet-ladder levels below the minimum bet — the explicit `config.minBet`, else\n * the currency-derived floor (5 minimal units) — so the smallest win can't round below one\n * minimal unit. Never returns empty (keeps the largest level if every level is below). */\nfunction applyMinBet(levels: number[], minBet: number | undefined, decimals: number): number[] {\n const minUnit = 10 ** -decimals;\n const floor = minBet ?? minUnit / MIN_PAYOUT_COEF;\n const filtered = levels.filter((b) => b >= floor - minUnit / 1000);\n return filtered.length ? filtered : [Math.max(...levels)];\n}\n\n/** Snap a wanted bet up to the first ladder level that is >= it (or the closest). */\nfunction snapToLadder(levels: number[], want: number): number {\n if (levels.includes(want)) return want;\n return levels.find((b) => b >= want) ?? levels[levels.length - 1] ?? want;\n}\n\n/** Server/replay-sourced values that drive the HUD spec — never taken from the game config. */\ninterface HudSpecInput {\n currency: string;\n betLevels: number[]; // major units — the authoritative ladder\n defaultBet: number; // major units\n rtp: number; // display %\n confirmBuyAboveCost: number; // jurisdiction policy\n}\n\n/** A read-only snapshot of the running game — for the dev harness, tests and debugging. */\nexport interface GameSnapshot {\n phase: string;\n balance: number;\n bet: number;\n lastWin: number;\n currency: string;\n spinning: boolean;\n}\n\nexport interface StakeGame {\n start(): Promise<void>;\n dispose(): void;\n /** Current phase + store values. The declarative harness asserts on this (no screenshots). */\n inspect(): GameSnapshot;\n /** Trigger a spin the same way the HUD spin button does — only from Idle. For harness/tests. */\n requestSpin(): boolean;\n}\n\nexport function createStakeGame<T = unknown, V = unknown, E = unknown>(opts: CreateStakeGameOptions<T, V, E>): StakeGame {\n const runtime = readRuntime({ overrides: opts.runtime });\n const network = opts.network ?? createNetwork(runtime);\n const stores = new RootStore();\n const ticker = new RealTicker();\n const turbo = new TurboClock(opts.config.turboSpeeds); // core-owned turbo speed + slam-stop\n // A ready AudioPort instance is used as-is; an AudioSpec is resolved lazily in start()\n // (the core creates the zvuk mixer from its own async chunk) — see resolveAudio().\n let audio: AudioPort | null = opts.audio && !isAudioSpec(opts.audio) ? opts.audio : null;\n const app = new Application();\n const disposers: Array<() => void> = [];\n let hud: BootedHud | null = null;\n let fsm: FSM<T, V, E> | null = null;\n let loader: GameLoader | null = null;\n\n // The battery wires the library's DESIGNED default icon set (the white Figma coins +\n // rotating-arrows spin skin) so every game gets the intended HUD out of the box — not\n // the lib's no-art placeholder geometry. `loadBuiltinArt` pulls the whole /art bundle\n // once; a game overrides any icon or the spin skin via `hudOptions.icons`/`spinSkin`.\n let builtinArt: ReturnType<typeof loadBuiltinArt> | null = null;\n const hudOpts = async (): Promise<Record<string, unknown>> => {\n builtinArt ??= loadBuiltinArt();\n const art = await builtinArt;\n const user = (opts.hudOptions ?? {}) as { icons?: Record<string, unknown>; spinSkin?: unknown };\n return {\n spinSkin: art.spinSkin,\n ...user,\n icons: { ...(art.icons as Record<string, unknown>), ...(user.icons ?? {}) },\n };\n };\n\n // The HUD spec is driven by SERVER-authoritative values (the ladder + confirm policy come\n // from `authenticate`/jurisdiction, not the game). `buildSpec` just formats them.\n const buildSpec = (s: HudSpecInput): Record<string, unknown> => ({\n currency: currencyFor(s.currency),\n betLadder: { levels: s.betLevels, index: Math.max(0, s.betLevels.indexOf(s.defaultBet)) },\n rtp: s.rtp,\n game: { name: opts.config.title, version: opts.config.version ?? '1.0.0' },\n // Stake owns fullscreen in its iframe — the game must not render its own. The bonus\n // (buy-feature) button shows only when a mode is a buy/boost card — it opens the feature list.\n controls: { fullscreen: { hidden: true }, ...(buyFeaturesOf(opts.config).length ? { bonus: { hidden: false } } : {}) },\n // Compliance: the buy-feature confirm threshold is jurisdiction policy, not a game knob.\n buyFeature: { confirmAboveCost: s.confirmBuyAboveCost },\n ...(opts.config.rules ? { menu: opts.config.rules } : {}),\n locale: {\n locale: runtime.language,\n messages: opts.config.messages ?? { en: {} },\n ...(opts.config.socialMessages ? { socialMessages: opts.config.socialMessages } : {}),\n },\n ...(opts.config.spec ?? {}),\n });\n\n const initApp = async (host: HTMLElement): Promise<void> => {\n await app.init({ resizeTo: window, backgroundAlpha: 0, antialias: true, resolution: Math.min(window.devicePixelRatio || 1, 2), autoDensity: true });\n host.appendChild(app.canvas);\n };\n\n // Build the mixer from a declarative AudioSpec. The `@stakeplate/core/audio` module (the\n // ONLY thing that pulls @schmooky/zvuk) is loaded via a DYNAMIC import, so it lands in its\n // own async chunk — an audio-less game never fetches zvuk. Sounds preload in the background.\n const resolveAudio = async (): Promise<void> => {\n if (!isAudioSpec(opts.audio)) return;\n const { createGameAudio } = await import('../audio');\n const mixer = createGameAudio(opts.audio);\n void mixer.load(opts.audio.sounds);\n audio = mixer;\n };\n\n async function start(): Promise<void> {\n const hudHost = opts.hudHost ?? document.body;\n const sceneHost = opts.sceneHost ?? hudHost;\n // Boot loader: paint it BEFORE anything heavy loads, then advance it across the milestones\n // below (init → auth → HUD → view) and fill + pop it away once the game is ready. `false`\n // (the default) renders none, so a game keeps whatever loader its HTML already has.\n if (opts.loader) loader = createLoader({ title: opts.config.title, ...opts.loader });\n const manualLoader = !!(opts.loader && opts.loader.manual);\n const phases = [...defaultPhases<T, V, E>(), ...(opts.phases ?? [])];\n const machine = (fsm = new FSM<T, V, E>(phases)); // outer `fsm` powers inspect()/requestSpin()\n loader?.setProgress(0.1);\n await resolveAudio(); // if `audio` is an AudioSpec, build the zvuk mixer (lazy chunk) + preload\n\n // ── REPLAY (Stake ?replay=true): fetch a round + play it back read-only ──────\n if (runtime.replay.active && network.replay) {\n try {\n await initApp(hudHost);\n loader?.setProgress(0.5);\n const cur = runtime.currency;\n const bet = runtime.replay.amount || 1; // replay carries its own bet; no ladder needed\n hud = mountHud(app, buildSpec({ currency: cur, betLevels: [bet], defaultBet: bet, rtp: opts.config.rtp ?? 96, confirmBuyAboveCost: STAKE_CONFIRM_ABOVE_COST }), (await hudOpts()) as never);\n stores.session.set({ currency: cur });\n stores.balance.setBalance(0);\n stores.balance.setBet(bet);\n if (runtime.social) hud.setSocial(true);\n loader?.setProgress(0.85);\n const view = opts.mountView(sceneHost, { config: opts.config, stores, hud, ticker, audio, turbo, loader });\n hud.setReplay(true);\n hud.lockInput();\n const cost = modeCostOf(opts.config, runtime.replay.mode);\n const raw = (await network.replay({ ...runtime.replay })) as Round<E>;\n const info = roundInfo(raw, bet, cost);\n const ctx = makeCtx(machine, view);\n ctx.round = { ...info, data: opts.interpretBook(raw, info), active: false, balance: 0, raw };\n const replayInfo = { baseBet: bet, costMultiplier: cost, payoutMultiplier: info.multiplier, amount: info.totalWin, currency: currencyFor(cur) };\n const playRound = async (): Promise<void> => {\n stores.ui.setSpinning(true);\n await machine.transition('present'); // game's Present plays it back → settle → idle\n hud!.replayEnd(replayInfo, () => void playRound());\n };\n hud.replayStart(replayInfo, () => void playRound());\n if (!manualLoader) void loader?.done();\n } catch (err) {\n loader?.remove();\n showBootError({ title: 'Cannot load the replay', message: 'The recorded round could not be loaded. Please reload to try again.', detail: errMsg(err) });\n }\n return;\n }\n\n // ── NORMAL BOOT ─────────────────────────────────────────────────────────────\n let auth;\n try {\n await initApp(hudHost);\n loader?.setProgress(0.35);\n auth = await network.authenticate();\n loader?.setProgress(0.6);\n } catch (err) {\n loader?.remove();\n showBootError({\n title: 'Cannot reach the game server',\n message: 'The game could not connect to or authenticate with the game server. Please reload to try again.',\n detail: errMsg(err),\n });\n return;\n }\n\n // Everything the HUD needs is SERVER-authoritative (from `authenticate`): the bet\n // ladder + default bet (per currency/jurisdiction), RTP, and the buy-confirm policy.\n const currency = auth.balance.currency;\n const juris = auth.config.jurisdiction ?? {};\n const rtp = auth.config.rtp ?? opts.config.rtp ?? 96;\n // Server ladder, trimmed by the client min-bet floor (explicit `config.minBet`, else the\n // currency-derived 5-minimal-units) so the smallest possible win can't round to sub-unit.\n const rawLevels = auth.config.betLevels.map((b) => b / API_AMOUNT_MULTIPLIER);\n const betLevels = applyMinBet(rawLevels, opts.config.minBet, currencyFor(currency).decimals ?? 2);\n const active = auth.round;\n const activeCost = active?.active ? modeCostOf(opts.config, active.mode) : 1;\n // Wanted default (restored from the active bet on resume), snapped up to the first legal\n // (floored) level so the ladder + store agree.\n const wantedDefault = active?.active\n ? active.amount / API_AMOUNT_MULTIPLIER / activeCost // resume: restore bet from the active amount\n : auth.config.defaultBetLevel / API_AMOUNT_MULTIPLIER;\n const defaultBet = snapToLadder(betLevels, wantedDefault);\n const confirmBuyAboveCost = juris.confirmBuyAboveCost ?? STAKE_CONFIRM_ABOVE_COST;\n\n hud = mountHud(app, buildSpec({ currency, betLevels, defaultBet, rtp, confirmBuyAboveCost }), (await hudOpts()) as never);\n hud.setCurrency(currencyFor(currency));\n hud.applyJurisdiction(juris);\n if (runtime.social || isSocialCurrency(currency)) hud.setSocial(true);\n\n stores.session.set({\n sessionId: runtime.sessionId,\n currency,\n rtp,\n availableBets: betLevels,\n jurisdiction: juris,\n });\n stores.balance.setBet(defaultBet);\n hud.setBet(defaultBet);\n loader?.setProgress(0.8);\n\n const view = opts.mountView(sceneHost, { config: opts.config, stores, hud, ticker, audio, turbo, loader });\n const ctx = makeCtx(machine, view);\n loader?.setProgress(0.95);\n\n // reactions store→HUD + HUD events\n disposers.push(reaction(() => stores.balance.balance, (b) => hud!.setBalance(b), { fireImmediately: false }));\n // The bet stepper is the base-bet source of truth. It emits `valueChanged` with\n // `id: 'bet-stepper'` and the ladder value (never the boosted display), so this feeds the\n // BASE bet even while an ante is active. (The lib mirrors it into `ui.bet`; the boost\n // reaction then overwrites the readout with the effective stake.)\n disposers.push(hud.on('valueChanged', (p) => { const v = p as { id?: string; value?: number }; if (v?.id === 'bet-stepper' && typeof v.value === 'number') stores.balance.setBet(v.value); }));\n // ── Spin triggers + turbo speed + autoplay (ALL core-owned) ─────────────────\n // One entry point for every spin: clears the slam-stop flag, then spins from idle.\n const beginSpin = (): void => {\n if (machine.current !== 'idle') return;\n turbo.resetSkip();\n void machine.transition('spin');\n };\n let holdActive = false; // press-and-hold turbo spin\n disposers.push(hud.on('spinRequested', beginSpin));\n // Autoplay/hold: the HUD owns the count picker, remaining count + RG limits (via\n // reportRound); the CORE runs the loop. Start on the first tick, then re-spin below.\n // Autoplay/hold both shorten game animations (turbo.setAutoplay) so long auto sessions\n // don't crawl. It's a floor on turbo speed — a faster turbo level still wins.\n disposers.push(hud.on('autoplayStarted', () => { turbo.setAutoplay(true); beginSpin(); }));\n disposers.push(hud.on('autoplayStopped', () => { turbo.setAutoplay(holdActive); }));\n disposers.push(hud.on('holdSpinStarted', () => { holdActive = true; turbo.setAutoplay(true); beginSpin(); }));\n disposers.push(hud.on('holdSpinStopped', () => { holdActive = false; turbo.setAutoplay(hud!.ui.autoplay.isActive); }));\n // Turbo speed (2-/3-mode cycler) + slam-stop (tap-to-skip the reels).\n disposers.push(hud.on('turboChanged', (p) => { const e = p as { index?: number }; if (typeof e.index === 'number') { turbo.setLevel(e.index); writePrefs({ turbo: e.index }); } }));\n disposers.push(hud.on('skipRequested', () => turbo.skip()));\n // Buy-feature: the bonus button opens the lib's feature-LIST modal (a card per buyable\n // mode). Two kinds of card, both routed through the jurisdiction confirm gate (no one-click\n // above the threshold):\n // • `buy` → one-shot. On confirm, spin that mode ONCE (SpinPhase charges its full cost).\n // • `boost` → a persistent ante. Activating sets it as the ACTIVE mode, so every following\n // base spin plays it at its full `cost×` (via `nextMode()`); toggling off restores base.\n // The modal owns opening/closing/confirm + single-activation; the core supplies the cards +\n // the onBuy/onActivate hooks and reflects the boosted stake in the bet readout.\n const features = buyFeaturesOf(opts.config);\n if (features.length) {\n // The bet readout shows the EFFECTIVE stake (base × the active ante's cost, emphasised)\n // while a boost is on, and the plain base bet otherwise — re-applied on bet/mode change.\n const applyBetDisplay = (): void => {\n const boost = stores.ui.activeMode;\n const cost = boost ? modeCostOf(opts.config, boost) : 1;\n hud!.ui.bet.set(stores.balance.bet * cost);\n (hud!.ui.bet as unknown as { setEmphasis?: (on: boolean) => void }).setEmphasis?.(cost !== 1);\n };\n disposers.push(\n mountBuyFeatureModal(app, hud, features, {\n activation: 'single', // one ante at a time (Stake)\n // Read the BASE bet straight from the stepper CONTROL (updated synchronously by\n // inc()/dec()) — NOT `stores.balance.bet`, which the core updates a tick later off\n // `valueChanged`, so the modal would lag one step (first +/- press \"doesn't\n // register\", +then- re-fires +). The readout still shows the boosted stake.\n getBet: () => hud!.ui.betStepper.value,\n onBuy: (id) => {\n if (machine.current !== 'idle') return;\n // Buying plays at the BASE bet — clear any active ante so its multiplied\n // (emphasised, yellow) bet readout reverts to the plain base bet first.\n if (stores.ui.activeMode) {\n stores.ui.setActiveMode(null);\n applyBetDisplay();\n }\n stores.ui.setOneShotMode(id);\n beginSpin();\n },\n onActivate: (ids) => {\n stores.ui.setActiveMode(ids[0] ?? null); // persistent ante; nextMode() prefers it\n applyBetDisplay();\n },\n }),\n );\n disposers.push(reaction(() => [stores.balance.bet, stores.ui.activeMode] as const, applyBetDisplay));\n }\n // Keep spinning while autoplay/hold is active: after each settled round (spinning\n // true → false), once idle, pause the (turbo-scaled) gap and spin again — stopping on\n // a blocking notice/error or when the next base stake is unaffordable.\n const autoplayGapMs = opts.config.autoplayGapMs ?? 250;\n disposers.push(\n reaction(\n () => stores.ui.spinning,\n (spinning, prev) => {\n if (prev !== true || spinning !== false) return;\n void turbo.delay(autoplayGapMs).then(() => {\n if (machine.current !== 'idle') return;\n if (!hud!.ui.autoplay.isActive && !holdActive) return;\n if (hud!.ui.noticeBlocks.get().length > 0 || stores.balance.balance < stores.balance.bet) {\n hud!.ui.autoplay.stop();\n holdActive = false;\n return;\n }\n beginSpin();\n });\n },\n ),\n );\n\n // Auto-wire audio ↔ HUD (Music/Effects sliders + mute, persisted) + unlock on the first\n // spin gesture — IF a mixer-like `audio` was provided. Structural check, no @schmooky/zvuk\n // import here, so audio-less games don't bundle it. (A game may bind manually instead.)\n const mixer = audio as unknown as MixerLike | null;\n if (mixer && typeof mixer.setGroupLevel === 'function') {\n disposers.push(bindMixerToHud(mixer, hud));\n if (typeof mixer.unlock === 'function') {\n const off = hud.on('spinRequested', () => { void mixer.unlock!(); off(); });\n disposers.push(off);\n }\n }\n // Input sounds: if the declarative AudioSpec named cues per HUD input, wire them now.\n if (audio && isAudioSpec(opts.audio) && opts.audio.inputSounds) {\n disposers.push(bindInputSounds(audio, hud, opts.audio.inputSounds));\n }\n\n // ── Persist player prefs (sound mute + turbo mode) across sessions ──────────\n // Restore BEFORE the game is interactive so the controls boot in the saved position\n // (setMuted also propagates to the audio mixer via bindMixerToHud's muted subscription\n // above). turboChanged below persists the turbo mode; anon/blocked-storage is safe.\n const prefs = readPrefs();\n if (typeof prefs.muted === 'boolean') hud.setMuted(prefs.muted);\n if (typeof prefs.turbo === 'number' && prefs.turbo > 0) {\n hud.ui.turbo.setIndex(prefs.turbo);\n turbo.setLevel(prefs.turbo);\n }\n disposers.push(hud.ui.muted.subscribe((m) => writePrefs({ muted: m })));\n\n // ── ACTIVE-ROUND RESUME: settle it, land idle, then a \"round in progress\" modal ──\n if (active?.active) {\n // The player refreshed while a round was still open. Settle it now (balance becomes\n // authoritative; the bet was restored into the ladder above), land on a clean idle\n // board, then show a NON-dismissible modal — on Continue we replay the recovered\n // round through the game's Present phase so the player watches how it resolved before\n // regaining control. (Not awaited — start() proceeds to fill/pop the loader; the modal\n // shows over the game once the loader fades.)\n const end = await network.endRound();\n const raw = active as Round<E>;\n const info = roundInfo(raw, defaultBet, activeCost);\n const settledBal = end.balance.amount / API_AMOUNT_MULTIPLIER;\n stores.balance.setBalance(settledBal);\n const resumeRound = { ...info, data: opts.interpretBook(raw, info), active: false, balance: settledBal, raw };\n await machine.transition('idle');\n hud.showFatal('You have an unfinished round. Continue to see how it ends.', {\n title: 'Round in progress',\n tone: 'info',\n actions: [\n {\n label: 'Continue',\n variant: 'primary',\n onSelect: () => {\n hud!.hideNotice();\n hud!.lockInput(); // no spins/buys while the recovered round replays\n ctx.round = resumeRound;\n void machine.transition('present').finally(() => hud!.unlockInput());\n },\n },\n ],\n });\n } else {\n stores.balance.setBalance(auth.balance.amount / API_AMOUNT_MULTIPLIER);\n await machine.transition('idle');\n }\n // The game is booted + interactive → fill + pop the loader away (unless the game asked\n // to drive it manually, e.g. to wait for its scene art via `ctx.loader.done()`).\n if (!manualLoader) void loader?.done();\n }\n\n function makeCtx(fsm: FSM<T, V, E>, view: V): PhaseContext<T, V, E> {\n const ctx: PhaseContext<T, V, E> = {\n config: opts.config,\n stores,\n network,\n hud: hud as BootedHud,\n ticker,\n view,\n audio,\n turbo,\n loader,\n interpretBook: opts.interpretBook,\n fsm,\n round: null,\n modeCost: (mode) => modeCostOf(opts.config, mode),\n };\n fsm.bind(ctx);\n return ctx;\n }\n\n function dispose(): void {\n for (const d of disposers.splice(0)) d();\n hud?.dispose();\n app.destroy(true);\n }\n\n function inspect(): GameSnapshot {\n return {\n phase: fsm?.current ?? 'boot',\n balance: stores.balance.balance,\n bet: stores.balance.bet,\n lastWin: stores.balance.lastWin,\n currency: stores.session.currency,\n spinning: stores.ui.spinning,\n };\n }\n\n function requestSpin(): boolean {\n if (!fsm || fsm.current !== 'idle') return false;\n void fsm.transition('spin');\n return true;\n }\n\n return { start, dispose, inspect, requestSpin };\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** An AudioSpec (declarative sounds) vs a ready AudioPort/GameAudio instance. */\nfunction isAudioSpec(a: unknown): a is AudioSpec {\n return !!a && typeof a === 'object' && Array.isArray((a as AudioSpec).sounds);\n}\n\nconst capitalize = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);\n\n/** The buyable/activatable modes (`modes.<key>.buy` / `.boost`) as buy-feature cards — the\n * list the bonus button opens. A plain number mode or one without `buy`/`boost` is not a card.\n * `cost` in config is the FULL play multiplier (× bet); a boost card shows the SURCHARGE it\n * adds over a base spin (`cost − 1`, e.g. a 2× ante → `+1× bet`), so the card + confirm read\n * right while the mode is still charged its full `cost` when it spins. */\nfunction buyFeaturesOf(config: GameConfig): FeatureSpec[] {\n const out: FeatureSpec[] = [];\n for (const [key, m] of Object.entries(config.modes ?? {})) {\n if (!m || typeof m !== 'object' || !(m.buy || m.boost)) continue;\n const variant = m.boost ? 'boost' : 'buy';\n out.push({ id: key, name: m.name ?? capitalize(key), variant, cost: variant === 'boost' ? m.cost - 1 : m.cost, image: m.image });\n }\n return out;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/engine/turbo.ts","../src/engine/round.ts","../src/engine/fsm.ts","../src/engine/phases.ts","../src/engine/beat.ts","../src/game/config.ts","../src/currency/index.ts","../src/loader/index.ts","../src/game/createStakeGame.ts"],"sourcesContent":["// Turbo speed + slam-stop, owned by the core. The HUD's turbo control (a 2- or 3-mode cycler)\n// drives a speed multiplier; the game's Present phase awaits `ctx.turbo.delay(ms)` for its\n// spin/anim durations, so turbo shortens them and a slam-stop (`skipRequested`) resolves them\n// instantly — with ZERO game logic. Compliance waits (minimumRoundDuration) use `ctx.ticker`,\n// NOT this, so they're never sped up or skipped.\n\n/** Delay multipliers per turbo level: off (1×) / turbo (0.4×) / super (0.12×). */\nexport const DEFAULT_TURBO_SPEEDS = [1, 0.4, 0.12];\n\nexport interface TurboState {\n /** 0 = off, 1 = turbo, 2 = super — the HUD turbo cycler's index. */\n readonly level: number;\n /** The EFFECTIVE delay multiplier right now — the turbo level, floored by the autoplay\n * speed while autoplay is running (`ctx.turbo.delay` already applies it). */\n readonly speed: number;\n /** True while autoplay/hold is running — game animations are shortened even at turbo off. */\n readonly autoplay: boolean;\n /** True once the player slam-stopped this round (delays resolve instantly). */\n readonly skipped: boolean;\n /** A turbo- + autoplay- + slam-stop-aware delay for GAME animations. NOT for compliance waits. */\n delay(ms: number): Promise<void>;\n}\n\nexport class TurboClock implements TurboState {\n private readonly speeds: number[];\n /** Delay multiplier applied while autoplay/hold runs (the floor for `speed`). Defaults to\n * the turbo (level-1) speed, so autoplay plays at least at turbo pace even at turbo off. */\n private readonly autoplaySpeed: number;\n private _level = 0;\n private _autoplay = false;\n private _skipped = false;\n private readonly pending = new Set<() => void>();\n\n constructor(speeds?: number[], autoplaySpeed?: number) {\n this.speeds = speeds && speeds.length ? speeds : DEFAULT_TURBO_SPEEDS;\n this.autoplaySpeed = autoplaySpeed ?? this.speeds[1] ?? 0.5;\n }\n\n get level(): number {\n return this._level;\n }\n get speed(): number {\n const levelSpeed = this.speeds[this._level] ?? this.speeds[this.speeds.length - 1] ?? 1;\n // Autoplay shortens animations even at turbo off — but never SLOWS a faster turbo level.\n return this._autoplay ? Math.min(levelSpeed, this.autoplaySpeed) : levelSpeed;\n }\n get autoplay(): boolean {\n return this._autoplay;\n }\n get skipped(): boolean {\n return this._skipped;\n }\n\n /** Set the turbo level (clamped to the configured speeds). */\n setLevel(index: number): void {\n const i = Number.isFinite(index) ? Math.trunc(index) : 0;\n this._level = Math.max(0, Math.min(i, this.speeds.length - 1));\n }\n\n /** Mark autoplay/hold as running so `delay()` shortens game animations. The core sets\n * this on autoplayStarted/holdSpinStarted and clears it when they stop. */\n setAutoplay(active: boolean): void {\n this._autoplay = active;\n }\n\n /** Slam-stop: resolve every in-flight delay + make the rest of the round instant. */\n skip(): void {\n this._skipped = true;\n const cbs = [...this.pending];\n this.pending.clear();\n for (const cb of cbs) cb();\n }\n\n /** Clear the slam-stop flag — the core calls this at the start of each spin. */\n resetSkip(): void {\n this._skipped = false;\n }\n\n delay(ms: number): Promise<void> {\n if (this._skipped || ms <= 0) return Promise.resolve();\n return new Promise<void>((resolve) => {\n let done = false;\n const finish = (): void => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n this.pending.delete(finish);\n resolve();\n };\n const timer = setTimeout(finish, ms * this.speed);\n this.pending.add(finish);\n });\n }\n}\n","// The round model the engine drives. The engine derives all MONEY (multiplier, win,\n// stake) from the raw wire round + the bet; the game's `interpretBook` only parses the\n// book EVENTS into its own model (`data`) — the one place a game touches a round.\n\nimport { API_AMOUNT_MULTIPLIER, BOOK_AMOUNT_MULTIPLIER, type Round } from '../rgs/protocol';\n\n/** Money facts of a settled round (all MAJOR units except `multiplier`, a ratio). */\nexport interface RoundInfo {\n mode: string;\n /** Base bet (before the mode's cost multiplier). */\n bet: number;\n /** Mode cost multiplier (× base bet) — the stake charged. */\n cost: number;\n /** The amount staked = bet × cost. */\n stake: number;\n /** Round multiplier relative to the BASE bet (totalWin / bet). */\n multiplier: number;\n /** Total win credited this round (derived: multiplier × bet). */\n totalWin: number;\n /** The server's AUTHORITATIVE win (`raw.payout`, major units); falls back to totalWin\n * when the round carries no explicit payout. Trust this over `totalWin` when they differ. */\n payout: number;\n}\n\n/**\n * A resolved round: money facts + the game's parsed model + the raw wire round. `E` is the\n * game's book-event type — declared once on `createStakeGame`, so `raw` (and `interpretBook`)\n * are TYPED rather than `unknown`.\n */\nexport interface GameRound<T = unknown, E = unknown> extends RoundInfo {\n /** The game's parsed model, from `interpretBook` — driven to the Present phase. */\n data: T;\n /** Whether the raw round still needs `/wallet/end-round` (the engine settles it). */\n active: boolean;\n /** Authoritative post-settlement balance (major units) — applied by the Settle phase. */\n balance: number;\n /** The raw, fully-typed wire round — the game's real server data. */\n raw: Round<E>;\n}\n\n/** The game's ONE money-logic seam: parse the raw round's (typed) events into your model. Pure. */\nexport type InterpretBook<T, E = unknown> = (raw: Round<E>, info: RoundInfo) => T;\n\n/** Build the money facts from a raw wire round + the base bet + the mode cost. */\nexport function roundInfo<E = unknown>(raw: Round<E>, bet: number, cost: number): RoundInfo {\n // Stake wire convention: `payoutMultiplier` is a plain Payout/Amount ratio (1× → 1)\n // in BOOK units (×100). totalWin is relative to the BASE bet; `payout` is the server's\n // own win amount (API units → major) when the round carries one.\n const multiplier = raw.payoutMultiplier / BOOK_AMOUNT_MULTIPLIER;\n const totalWin = multiplier * bet;\n const payout = raw.payout != null ? raw.payout / API_AMOUNT_MULTIPLIER : totalWin;\n return { mode: raw.mode, bet, cost, stake: bet * cost, multiplier, totalWin, payout };\n}\n","// The round state machine. A round is an explicit sequence of awaited phases\n// (Idle → Spin → Present → Settle); each `enter` does its work + transitions on. The\n// core ships Idle/Spin/Settle; the game writes Present (and may override any). The FSM\n// + context are pure — the HUD/view/audio arrive via ports, so a whole round runs\n// headless (see the InstantTicker + MockNetworkManager).\n\nimport type { NetworkManager } from '../rgs/network';\nimport type { RootStore } from '../stores/index';\nimport type { Ticker } from './ticker';\nimport type { TurboState } from './turbo';\nimport type { HudPort } from './hud-port';\nimport type { GameRound, InterpretBook } from './round';\nimport type { GameConfig } from '../game/config';\n\n/** A fixed value or a per-voice random spread (`{ base, jitter }`) — structurally matches\n * zvuk's `VoiceJitter` without importing it, so the engine stays zvuk-free. */\nexport type AudioValue = number | { base?: number; jitter?: number };\n\n/** The minimal boot-loader surface a game's phases touch (satisfied by the core's\n * `GameLoader`) — kept structural so the engine needn't import the loader module. */\nexport interface LoaderPort {\n setProgress(p: number): void;\n done(): Promise<void>;\n}\n\n/** The minimal audio surface a game's phases call (satisfied by `@stakeplate/core/audio`). */\nexport interface AudioPort {\n /** Fire a one-shot. `volume`/`pitch` accept jitter so repeated cues vary per voice. */\n play(name: string, opts?: { bus?: string; volume?: AudioValue; pitch?: AudioValue }): void;\n music(name: string, opts?: { fadeIn?: number }): void;\n stopMusic(opts?: { fade?: number }): void;\n}\n\n/** Handed to every phase. `round` is set by SpinPhase and read by Present/Settle. `E` is\n * the game's book-event type, so `interpretBook`/`round.raw` are typed. */\nexport interface PhaseContext<T = unknown, V = unknown, E = unknown> {\n readonly config: GameConfig;\n readonly stores: RootStore;\n readonly network: NetworkManager;\n readonly hud: HudPort;\n readonly ticker: Ticker;\n /** The game's mounted view (scene/presenter/stores) — whatever `mountView` returned. */\n readonly view: V;\n readonly audio: AudioPort | null;\n /** Turbo speed + slam-stop (core-owned). Use `ctx.turbo.delay(ms)` for spin/anim timing. */\n readonly turbo: TurboState;\n /** The boot loader, if one was configured (else `null`) — drive progress / `done()`. */\n readonly loader: LoaderPort | null;\n readonly interpretBook: InterpretBook<T, E>;\n readonly fsm: FSM<T, V, E>;\n round: GameRound<T, E> | null;\n /** Cost multiplier for a mode key (from `config.modes`, default 1). */\n modeCost(mode: string): number;\n}\n\nexport interface Phase<T = unknown, V = unknown, E = unknown> {\n readonly name: string;\n enter(ctx: PhaseContext<T, V, E>): Promise<void> | void;\n exit?(ctx: PhaseContext<T, V, E>): void;\n}\n\nexport class FSM<T = unknown, V = unknown, E = unknown> {\n private ctx: PhaseContext<T, V, E> | null = null;\n private readonly byName = new Map<string, Phase<T, V, E>>();\n private _current = '';\n\n /** Later phases with the same `name` win — so a game's Present overrides the default. */\n constructor(phases: Phase<T, V, E>[]) {\n for (const p of phases) this.byName.set(p.name, p);\n }\n\n bind(ctx: PhaseContext<T, V, E>): void {\n this.ctx = ctx;\n }\n\n get current(): string {\n return this._current;\n }\n\n has(name: string): boolean {\n return this.byName.has(name);\n }\n\n async transition(name: string): Promise<void> {\n if (!this.ctx) throw new Error('[FSM] not bound to a context');\n const next = this.byName.get(name);\n if (!next) throw new Error(`[FSM] unknown phase: ${name}`);\n this.byName.get(this._current)?.exit?.(this.ctx);\n this._current = name;\n await next.enter(this.ctx);\n }\n}\n","// The default round phases (Idle → Spin → Present → Settle). The game writes Present\n// (animate the round) and may override any of these. Balance/bet flow through the\n// stores (createStakeGame reacts store→HUD); the phases call the HUD directly only for\n// reportRound + errors.\n\nimport { API_AMOUNT_MULTIPLIER, type Round } from '../rgs/protocol';\nimport { roundInfo } from './round';\nimport type { Phase, PhaseContext } from './fsm';\n\n/** Wait for the next spin (the HUD's `spinRequested` drives the transition to Spin). */\nexport class IdlePhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'idle';\n enter(ctx: PhaseContext<T, V, E>): void {\n ctx.stores.ui.setSpinning(false);\n }\n}\n\n/** Pick the mode, take the stake, ask the RGS, parse the round, hand off to Present. */\nexport class SpinPhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'spin';\n async enter(ctx: PhaseContext<T, V, E>): Promise<void> {\n const { stores, network, hud } = ctx;\n const mode = stores.ui.nextMode();\n const cost = ctx.modeCost(mode);\n const bet = stores.balance.bet;\n const stake = bet * cost;\n\n stores.ui.setSpinning(true);\n stores.balance.debitStake(stake); // stake leaves now; the win lands at Settle\n\n try {\n const play = await network.play({ bet, mode });\n // The transport is game-agnostic (events: unknown); the game declared `E`, so we\n // narrow here — the game owns the shape it parses in `interpretBook`.\n const raw = play.round as Round<E>;\n const info = roundInfo(raw, bet, cost);\n const data = ctx.interpretBook(raw, info);\n // DO NOT /wallet/end-round here. An ACTIVE round is left OPEN so a refresh DURING\n // Present (the game's reel/scene animation) can recover it — SettlePhase settles it\n // once the round has finished playing out. For an active round `play.balance` is\n // post-DEBIT (stake gone, win not yet credited), so the post-win balance the player\n // sees at Settle is play.balance + the (authoritative-from-book) win; SettlePhase then\n // reconciles to the server's own settled figure. A round that is already settled (a\n // loss, or an immediate-settle mock) reports its FINAL balance in play.balance — use\n // it verbatim, or the win would be double-counted.\n const settledMoney = play.balance.amount / API_AMOUNT_MULTIPLIER;\n ctx.round = {\n ...info,\n data,\n active: raw.active ?? false,\n balance: raw.active ? settledMoney + info.totalWin : settledMoney,\n raw,\n };\n } catch (err) {\n stores.balance.refund(stake); // the round never happened\n stores.ui.setSpinning(false);\n const msg = err instanceof Error ? err.message : String(err);\n const code = msg.match(/ERR_[A-Z_]+/)?.[0];\n if (code) hud.showRgsError(code);\n else hud.showError(msg);\n await ctx.fsm.transition('idle');\n return;\n }\n\n await ctx.fsm.transition('present');\n }\n}\n\n/** DEFAULT Present — nothing to animate. The game overrides `present` to play its scene. */\nexport class PresentPhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'present';\n async enter(ctx: PhaseContext<T, V, E>): Promise<void> {\n await ctx.fsm.transition('settle');\n }\n}\n\n/** Apply the authoritative balance + win, settle the round for the HUD, then Idle. */\nexport class SettlePhase<T = unknown, V = unknown, E = unknown> implements Phase<T, V, E> {\n readonly name = 'settle';\n private roundStartedAt = 0;\n\n async enter(ctx: PhaseContext<T, V, E>): Promise<void> {\n const r = ctx.round;\n if (r) {\n ctx.stores.balance.settle(r.balance, r.totalWin);\n ctx.hud.reportRound(r.totalWin, r.bet);\n // minimumRoundDuration: pad to the jurisdiction minimum (best-effort; the boot\n // records the round start, so this covers the whole spin→settle span).\n const minMs = ctx.stores.session.jurisdiction.minimumRoundDuration ?? 0;\n const elapsed = (ctx.ticker.now() - this.roundStartedAt) * 1000;\n if (minMs > 0 && elapsed < minMs) await ctx.ticker.delay(minMs - elapsed);\n // The round has now finished playing out visually (Present + the min-duration pad).\n // ONLY NOW settle it on the server — SpinPhase deliberately left it OPEN so a refresh\n // mid-Present recovers the round instead of losing it. The win already shows (from the\n // book); this reconciles to the server's authoritative post-settlement balance.\n if (r.active) {\n try {\n const end = await ctx.network.endRound();\n ctx.stores.balance.setBalance(end.balance.amount / API_AMOUNT_MULTIPLIER);\n } catch (err) {\n // The win is already credited locally; a settle hiccup self-heals on the next\n // authenticate (the RGS reports the still-open round). Surface it, don't crash.\n console.warn('[settle] end-round failed; balance reconciles on next authenticate:', err);\n }\n }\n }\n ctx.stores.ui.setSpinning(false);\n await ctx.fsm.transition('idle');\n }\n\n /** Called by SpinPhase-adjacent boot wiring to timestamp the round start. */\n markStart(now: number): void {\n this.roundStartedAt = now;\n }\n}\n\n/** The default phase set (game phases are appended after → same-name overrides win). */\nexport function defaultPhases<T = unknown, V = unknown, E = unknown>(): Phase<T, V, E>[] {\n return [new IdlePhase<T, V, E>(), new SpinPhase<T, V, E>(), new PresentPhase<T, V, E>(), new SettlePhase<T, V, E>()];\n}\n","// A blocking \"announcement beat\" — the pattern behind moments like a bonus trigger or a\n// \"… ELIMINATED!\" banner: pause so the reveal settles, SHOW the announcement, HOLD it\n// alone on screen, HIDE it, then a trailing pause before play resumes. The point is that\n// the round never plays UNDER the announcement — everything waits for the beat.\n//\n// The game owns the visuals (`show`/`hide` — mount a banner, toggle a class, play an sfx);\n// the core owns the timing + sequencing so every game's beats feel consistent.\n\nimport type { Ticker } from './ticker';\nimport type { TurboState } from './turbo';\n\nexport interface BeatOptions {\n /** Pause before the announcement shows — lets the reveal settle. Default 0. */\n leadMs?: number;\n /** How long the announcement holds alone on screen. */\n holdMs: number;\n /** Pause after hiding, before play resumes. Default 0. */\n trailMs?: number;\n /** Reveal the announcement (banner/sfx). Called after the lead pause. */\n show: () => void;\n /** Hide it. Called after the hold, before the trailing pause. Optional (some banners\n * self-dismiss). */\n hide?: () => void;\n /**\n * Timing source. `'real'` (default) is a fixed, unskippable beat via the ticker — an\n * announcement is a deliberate moment that a slam-stop shouldn't blow past. `'turbo'`\n * makes the pauses turbo-/autoplay-/slam-stop-aware (shorter under turbo, skippable).\n */\n timing?: 'real' | 'turbo';\n}\n\n/** Timing surface the beat needs — a `PhaseContext` satisfies it (`ctx.ticker`/`ctx.turbo`). */\nexport interface BeatClock {\n ticker: Ticker;\n turbo: TurboState;\n}\n\n/**\n * Run a blocking announcement beat: lead → show → hold → hide → trail. Awaitable, so a\n * Present phase just `await blockingBeat(ctx, { … })` and the round pauses for the whole\n * thing. Returns when the beat (including the trailing pause) is over.\n */\nexport async function blockingBeat(ctx: BeatClock, opts: BeatOptions): Promise<void> {\n const wait = opts.timing === 'turbo' ? (ms: number) => ctx.turbo.delay(ms) : (ms: number) => ctx.ticker.delay(ms);\n if (opts.leadMs && opts.leadMs > 0) await wait(opts.leadMs);\n opts.show();\n await wait(opts.holdMs);\n opts.hide?.();\n if (opts.trailMs && opts.trailMs > 0) await wait(opts.trailMs);\n}\n","// The per-game configuration passed to `createStakeGame`. Everything the core needs to\n// stand up the HUD + drive the round; the game's mechanics live in its scene + phases.\n\nexport interface ModeConfig {\n /** Cost multiplier (× base bet). `base` is 1. */\n cost: number;\n /** A one-shot bought bonus — shown as a `Buy` card in the buy-feature modal (opened by the\n * bonus button). Buying it spins this mode once (through the jurisdiction confirm gate). */\n buy?: boolean;\n /** An activatable per-spin bet surcharge — shown as an `Activate` card (vs a one-tap `Buy`). */\n boost?: boolean;\n /** Display name for the feature card (defaults to the capitalized mode key). */\n name?: string;\n /** Card art (URL or data URI) for the feature card. A neutral gradient is used when absent. */\n image?: string;\n}\n\nexport interface GameConfig {\n title: string;\n version?: string;\n /** Fallback currency code; the session/`?currency=` wins. */\n currency?: string;\n /**\n * Theoretical RTP percentage — DISPLAY ONLY (the RTP readout + rules). NOT authoritative:\n * the server (`auth.config.rtp`) wins, and the certified math report is the source of\n * truth. This is only a last-resort fallback; prefer never hand-typing it (drift risk).\n */\n rtp?: number;\n /** Mode key → cost multiplier (a number) or a `ModeConfig`. `base` defaults to 1. */\n modes?: Record<string, ModeConfig | number>;\n /**\n * Minimum selectable bet, in MAJOR units of the account currency — an EXPLICIT client\n * floor. Any server bet-ladder level below this is dropped from the HUD ladder, so the\n * smallest possible win (bet × the game's minimum payout) can never round below one\n * minimal currency unit. Set it per game to your smallest legal stake — e.g. `0.05` when\n * the minimum payout is ×0.2 (0.05 × 0.2 = 0.01 = one cent). `undefined` → derive it from\n * the currency (5 minimal units — a safe default for a ×0.2 minimum payout). The server\n * ladder stays authoritative for the LEVELS offered; this only trims the low end + snaps\n * the default up to the first legal level.\n */\n minBet?: number;\n /** Rules/info menu for the HUD (`@open-slot-ui` `MenuSpec`) — build it with `@stakeplate/core/rules` `buildRules`. */\n rules?: unknown;\n /** i18n messages per locale (`{ en: { key: text }, es: {…} }`) for the HUD + rules text. */\n messages?: Record<string, Record<string, string>>;\n /** SOCIAL/sweepstakes wording per locale — swapped in when social mode is on. Merge in\n * `buildRules().socialEn` so the core's disclaimer/guide are social-safe. */\n socialMessages?: Record<string, Record<string, string>>;\n /** Delay multipliers per turbo level (off / turbo / super). Default `[1, 0.4, 0.12]`. */\n turboSpeeds?: number[];\n /** Pause (ms) between autoplay/hold spins. Default 250. Scales with turbo. */\n autoplayGapMs?: number;\n /** Extra `@open-slot-ui` `UISpec` fields merged into the built spec (escape hatch). */\n spec?: Record<string, unknown>;\n}\n\n// NOTE (server-authoritative): the bet ladder (`betLevels` + `defaultBetLevel`) and the\n// buy-feature confirm threshold are NOT game config — the RGS `authenticate` response owns\n// the ladder (per currency/jurisdiction) and the jurisdiction owns the confirm policy. The\n// core reads them from `auth.config`; a game only declares that a mode is a buy (`modes`).\n\n/** The cost multiplier for a mode key (default 1 for `base` / unknown modes). */\nexport function modeCostOf(config: GameConfig, mode: string): number {\n const m = config.modes?.[mode];\n if (typeof m === 'number') return m;\n if (m && typeof m === 'object') return m.cost;\n return 1;\n}\n","// @stakeplate/core/currency — currency resolution with the 3-decimal fiat the\n// @open-slot-ui table doesn't (yet) carry patched in.\n//\n// The lib's `resolveCurrency` knows the common codes (USD 2dp, JPY 0dp, BTC 8dp, the\n// Kuwaiti/Bahraini/Jordanian dinars KWD/BHD/JOD 3dp, the Stake social coins, …). For a\n// code it DOESN'T know it falls back to 2 decimals — which silently truncates the sub-unit\n// of the other three-decimal currencies. That matters: e.g. the Omani Rial's minimal\n// stake 0.01 at the lowest ×0.2 coefficient is a 0.002 win, which renders as \"0.00\" at\n// two decimals. `currencyFor` passes the correct precision (+ a sensible symbol) through\n// `resolveCurrency`'s overrides so the full digits show end-to-end (bet, balance, net,\n// win plaque, buy modal, replay).\n\nimport { resolveCurrency, type CurrencySpec } from '@open-slot-ui/core';\n\n/** Three-decimal fiat missing from the @open-slot-ui currency table — the Gulf/Arab\n * dinars & rials (the lib already covers KWD/BHD/JOD). `symbol` follows the lib's short\n * latinised convention for the region (KWD→\"KD\", …). */\nexport const EXTRA_DECIMALS: Readonly<Record<string, { decimals: number; symbol: string }>> = {\n OMR: { decimals: 3, symbol: 'OMR' }, // Omani Rial\n TND: { decimals: 3, symbol: 'DT' }, // Tunisian Dinar\n LYD: { decimals: 3, symbol: 'LD' }, // Libyan Dinar\n IQD: { decimals: 3, symbol: 'IQD' }, // Iraqi Dinar\n};\n\n/** Symbol-only fixes required by Stake's RGS-communication approval guidelines, layered on\n * top of the @open-slot-ui table (which is otherwise correct on precision + symbol side).\n * Danish Krone must read \"KR\" — the lib ships lowercase \"kr\". Taiwan Dollar already resolves\n * to the accepted \"NT$\", so it needs no override. */\nexport const SYMBOL_OVERRIDES: Readonly<Record<string, string>> = {\n DKK: 'KR', // Danish Krone → \"KR\"\n};\n\n/**\n * `resolveCurrency`, but with the missing 3-decimal dinars/rials patched in and the\n * approval-mandated symbol fixes applied. Use this everywhere a currency code becomes a\n * `CurrencySpec` so precision + symbol are correct for every code — `createStakeGame`\n * routes all of its resolution through here.\n */\nexport function currencyFor(code: string): CurrencySpec {\n const up = code ? code.toUpperCase() : '';\n const symbol = up ? SYMBOL_OVERRIDES[up] : undefined;\n const overrides = { ...(up ? EXTRA_DECIMALS[up] : undefined), ...(symbol ? { symbol } : undefined) };\n return Object.keys(overrides).length ? resolveCurrency(code, overrides) : resolveCurrency(code);\n}\n","// The boot experience — a single overlay that carries the player from LOADING straight into\n// an optional FEATURES splash and then out of the way, all in one continuous element (no\n// flash between screens). `createStakeGame({ loader })` shows it the instant boot starts:\n//\n// LOADING — logo (pulsing) + a progress bar the core advances across the boot milestones\n// (init → auth → HUD → view), over an optional blurred cover image + vignette.\n// FEATURES — (if `features` are configured) once boot is ready the SAME logo glides up to\n// the top, the bar fades out, and 2–3 feature tiles (image + short caption, all\n// scaled to one size) fade + scale in with a \"press to continue\" prompt.\n// REVEAL — a tap / key press (or auto, with no features) fades the overlay away to the\n// game, which is already booted + idle behind it.\n//\n// So the game gets a `loader → features → idle` flow for free — it only writes its `present`\n// phase. Pure DOM + CSS: it paints before pixi/zvuk load and removes itself when done.\n\nlet seq = 0;\n\n/** One feature card on the intro splash — an image with a short (may be multi-line) caption. */\nexport interface FeatureItem {\n /** Image URL / data-URI. Rendered at a uniform height so mismatched art still lines up. */\n image: string;\n /** Short caption under the image. `\\n` for line breaks. */\n text: string;\n}\n\nexport interface LoaderConfig {\n /** Big title under the logo (LOADING phase only). Defaults to the game title. */\n title?: string;\n /** Small line under the title (LOADING phase). */\n subtitle?: string;\n /** URL/data-URI of the logo. Pulses while loading; glides to the top for the features\n * splash. Omit for a CSS ring spinner (and no move-up). */\n logo?: string;\n /** Solid colour behind everything. Default `#0b0d12`. */\n background?: string;\n /** Optional cover image (ideally the game's own backdrop, for a seamless reveal). */\n backgroundImage?: string;\n /** Blur the cover image. Default `true`. */\n blur?: boolean;\n /** Accent colour — progress fill + title glow + caption text. Default `#d99000`. */\n accent?: string;\n /** Minimum time the LOADING phase stays up, so the bar always fills. Default `1200`ms. */\n minDurationMs?: number;\n /** Stacking order. Default a very high value so it sits over the game. */\n zIndex?: number;\n /**\n * If `true`, the core shows the loader + advances progress but does NOT auto-advance past\n * LOADING — the game calls `ctx.loader.done()` itself (e.g. after its scene finishes loading\n * art). Default `false`. (The FEATURES splash always waits for the player regardless.)\n */\n manual?: boolean;\n /** 2–3 feature cards → show a FEATURES splash after loading. Omit for a plain loader. */\n features?: FeatureItem[];\n /** The dismiss prompt under the feature cards. Default `PRESS TO CONTINUE`. */\n continueText?: string;\n}\n\nexport interface GameLoader {\n /** The overlay element (already in the document). */\n readonly el: HTMLElement;\n /** Drive the progress bar. Clamped to the range it has already reached (never goes back). */\n setProgress(p: number): void;\n /** Advance out of LOADING: fill to 100%, honour `minDurationMs`, then either show the\n * FEATURES splash (waiting for the player to continue) or — with no features — pop away.\n * Resolves once the overlay is fully gone. Idempotent (first call wins). */\n done(): Promise<void>;\n /** Remove immediately without any outro (used on a boot error). */\n remove(): void;\n}\n\nconst isBrowser = (): boolean => typeof document !== 'undefined' && !!document.body;\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\n/** Create + mount the boot overlay. Safe to call before anything else in boot. */\nexport function createLoader(config: LoaderConfig = {}): GameLoader {\n const startedAt = Date.now();\n const minMs = config.minDurationMs ?? 1200;\n const accent = config.accent ?? '#d99000';\n const bg = config.background ?? '#0b0d12';\n const z = config.zIndex ?? 2147483000;\n const blur = config.blur ?? true;\n const features = config.features ?? [];\n const hasLogo = !!config.logo;\n let progress = 0;\n let donePromise: Promise<void> | null = null;\n\n // Headless / SSR guard — a no-op handle so callers needn't branch.\n if (!isBrowser()) {\n return { el: null as unknown as HTMLElement, setProgress() {}, done: () => Promise.resolve(), remove() {} };\n }\n\n const id = `sp-loader-${++seq}`;\n const root = document.createElement('div');\n root.id = id;\n root.className = 'sp-loader phase-loading';\n\n const style = document.createElement('style');\n style.textContent = `\n #${id}{position:fixed;inset:0;z-index:${z};background:${config.backgroundImage ? 'transparent' : bg};overflow:hidden;\n font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;\n opacity:1;transition:opacity .5s ease}\n #${id}.sp-done{opacity:0}\n #${id} .sp-bgimg{position:absolute;inset:-8%;background-position:center;background-size:cover;\n ${blur ? 'filter:blur(6px);' : ''}transform:scale(1.06)}\n #${id} .sp-vign{position:absolute;inset:0;background:radial-gradient(120% 90% at 50% 42%,transparent 40%,rgba(0,0,0,.62) 100%)}\n /* Logo — same element in both phases; glides + shrinks from centre to the top. */\n #${id} .sp-logo{position:absolute;left:50%;top:36%;transform:translate(-50%,-50%);\n width:min(42vw,240px);height:auto;filter:drop-shadow(0 14px 24px rgba(0,0,0,.55));\n animation:sp-pulse 1.5s ease-in-out infinite;\n transition:top .6s cubic-bezier(.5,0,.2,1),width .6s cubic-bezier(.5,0,.2,1)}\n #${id}.phase-features .sp-logo{top:15%;width:min(30vw,180px);animation:none}\n /* LOADING group (title/subtitle + bar) — fades out on the way to features. */\n #${id} .sp-load{position:absolute;left:50%;top:58%;transform:translate(-50%,-50%);\n display:flex;flex-direction:column;align-items:center;gap:14px;\n transition:opacity .35s ease}\n #${id}.phase-features .sp-load{opacity:0;pointer-events:none}\n #${id} .sp-spin{width:60px;height:60px;border-radius:50%;border:5px solid rgba(255,255,255,.18);\n border-top-color:${accent};animation:sp-rot .8s linear infinite}\n #${id} .sp-title{margin:0;color:#fff;font-weight:800;letter-spacing:.02em;\n font-size:clamp(20px,3.6vw,36px);text-align:center;text-shadow:0 3px 10px rgba(0,0,0,.55),0 0 22px ${accent}55}\n #${id} .sp-sub{margin:0;color:#ffffffcc;font-size:clamp(12px,2vw,16px);text-align:center;text-shadow:0 2px 6px rgba(0,0,0,.5)}\n #${id} .sp-bar{position:relative;width:min(60vw,260px);height:8px;border-radius:99px;background:rgba(255,255,255,.16);overflow:hidden}\n #${id} .sp-fill{position:absolute;left:0;top:0;bottom:0;width:0%;border-radius:99px;background:${accent};transition:width .35s ease}\n /* FEATURES splash — tiles + prompt; scales/fades in once boot is ready. */\n #${id} .sp-feat{position:absolute;left:0;right:0;top:30%;bottom:0;display:flex;flex-direction:column;\n align-items:center;justify-content:flex-start;gap:5vh;padding:0 4vw;\n opacity:0;transform:scale(.94);pointer-events:none;transition:opacity .5s ease,transform .5s ease}\n #${id}.phase-features .sp-feat{opacity:1;transform:scale(1);pointer-events:auto}\n #${id} .sp-tiles{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;\n gap:clamp(18px,5vw,64px);max-width:94vw}\n #${id} .sp-tile{display:flex;flex-direction:column;align-items:center;gap:14px;\n width:clamp(110px,22vw,210px);opacity:0;transform:translateY(16px) scale(.9);\n transition:opacity .45s ease,transform .45s cubic-bezier(.34,1.56,.64,1)}\n #${id}.phase-features .sp-tile{opacity:1;transform:none}\n #${id} .sp-tile img{height:clamp(66px,13vh,150px);width:auto;max-width:100%;object-fit:contain;\n filter:drop-shadow(0 8px 14px rgba(0,0,0,.45))}\n #${id} .sp-tile span{color:${accent};font-weight:800;text-transform:uppercase;letter-spacing:.02em;\n text-align:center;white-space:pre-line;line-height:1.15;font-size:clamp(13px,2.1vw,21px);\n text-shadow:0 2px 6px rgba(0,0,0,.6)}\n #${id} .sp-cont{margin-top:auto;margin-bottom:5vh;color:#ffffffcc;text-transform:uppercase;\n letter-spacing:.16em;font-size:clamp(11px,1.6vw,15px);animation:sp-blink 1.7s ease-in-out infinite}\n @keyframes sp-pulse{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-50%) scale(1.06)}}\n @keyframes sp-rot{to{transform:rotate(360deg)}}\n @keyframes sp-blink{0%,100%{opacity:.4}50%{opacity:1}}\n @media (prefers-reduced-motion:reduce){#${id} .sp-logo,#${id} .sp-spin,#${id} .sp-cont{animation:none}}\n `;\n\n if (config.backgroundImage) {\n const img = document.createElement('div');\n img.className = 'sp-bgimg';\n img.style.backgroundImage = `url(${config.backgroundImage})`;\n root.appendChild(img);\n const vign = document.createElement('div');\n vign.className = 'sp-vign';\n root.appendChild(vign);\n }\n\n if (hasLogo) {\n const logo = document.createElement('img');\n logo.className = 'sp-logo';\n logo.src = config.logo!;\n logo.alt = '';\n root.appendChild(logo);\n }\n\n // LOADING group.\n const load = document.createElement('div');\n load.className = 'sp-load';\n if (!hasLogo) {\n const spin = document.createElement('div');\n spin.className = 'sp-spin';\n load.appendChild(spin);\n }\n if (config.title) {\n const h = document.createElement('h1');\n h.className = 'sp-title';\n h.textContent = config.title;\n load.appendChild(h);\n }\n if (config.subtitle) {\n const p = document.createElement('p');\n p.className = 'sp-sub';\n p.textContent = config.subtitle;\n load.appendChild(p);\n }\n const bar = document.createElement('div');\n bar.className = 'sp-bar';\n const fill = document.createElement('div');\n fill.className = 'sp-fill';\n bar.appendChild(fill);\n load.appendChild(bar);\n root.appendChild(load);\n\n // FEATURES group (built only when configured).\n if (features.length) {\n const feat = document.createElement('div');\n feat.className = 'sp-feat';\n const tiles = document.createElement('div');\n tiles.className = 'sp-tiles';\n features.forEach((f, i) => {\n const tile = document.createElement('div');\n tile.className = 'sp-tile';\n tile.style.transitionDelay = `${0.12 + i * 0.12}s`; // staggered entrance\n const img = document.createElement('img');\n img.src = f.image;\n img.alt = '';\n const cap = document.createElement('span');\n cap.textContent = f.text;\n tile.append(img, cap);\n tiles.appendChild(tile);\n });\n const cont = document.createElement('div');\n cont.className = 'sp-cont';\n cont.textContent = config.continueText ?? 'PRESS TO CONTINUE';\n feat.append(tiles, cont);\n root.appendChild(feat);\n }\n\n root.appendChild(style);\n document.body.appendChild(root);\n\n const setProgress = (p: number): void => {\n const next = Math.max(progress, Math.min(1, Number.isFinite(p) ? p : 0));\n progress = next;\n fill.style.width = `${Math.round(next * 100)}%`;\n };\n\n const remove = (): void => {\n root.remove();\n style.remove();\n // Clean up the build-time boot backdrop injected by the `stakeplateBoot` Vite plugin\n // (if present) — the game is now covering it.\n document.getElementById('sp-bootbg')?.remove();\n document.getElementById('sp-boot-style')?.remove();\n };\n\n // Transition LOADING → FEATURES, then resolve once the player dismisses it.\n const runFeatures = (): Promise<void> =>\n new Promise((resolve) => {\n root.classList.remove('phase-loading');\n root.classList.add('phase-features');\n const dismiss = (): void => {\n root.removeEventListener('pointerdown', dismiss);\n window.removeEventListener('keydown', onKey);\n resolve();\n };\n const onKey = (e: KeyboardEvent): void => { if (e.key === ' ' || e.key === 'Enter') dismiss(); };\n // Arm dismissal only after the splash has animated in (so a stray click can't skip it).\n setTimeout(() => {\n root.addEventListener('pointerdown', dismiss);\n window.addEventListener('keydown', onKey);\n }, 500);\n });\n\n const done = (): Promise<void> => {\n if (donePromise) return donePromise;\n donePromise = (async () => {\n setProgress(1);\n const wait = minMs - (Date.now() - startedAt);\n if (wait > 0) await sleep(wait);\n if (features.length) await runFeatures(); // hold on the splash until the player continues\n root.classList.add('sp-done'); // fade the whole overlay out\n await sleep(520);\n remove();\n })();\n return donePromise;\n };\n\n return { el: root, setProgress, done, remove };\n}\n","// createStakeGame — the one-call façade. `start()` runs the whole compliant boot with\n// zero game involvement: runtime → network → HUD → (replay | authenticate → configure\n// HUD → resume active round → wire events) → run the FSM. Connection/auth failure →\n// the themed, blocking showBootError. The game supplies only config + interpretBook +\n// mountView (+ optional Present phase, audio). This is the ONLY module that imports the\n// HUD/pixi peers; the engine stays decoupled behind ports.\n\nimport { Application } from 'pixi.js';\nimport { mountHud, showBootError, mountBuyFeatureModal, type BootedHud, type FeatureSpec } from '@open-slot-ui/pixi';\nimport { loadBuiltinArt } from '@open-slot-ui/pixi/art';\nimport { isSocialCurrency } from '@open-slot-ui/core';\nimport { currencyFor } from '../currency';\nimport { reaction } from 'mobx';\nimport { readRuntime, type RuntimeConfig } from '../rgs/runtime';\nimport { createNetwork, type NetworkManager } from '../rgs/network';\nimport { API_AMOUNT_MULTIPLIER, type Round } from '../rgs/protocol';\nimport { RootStore } from '../stores/index';\nimport { RealTicker, type Ticker } from '../engine/ticker';\nimport { TurboClock, type TurboState } from '../engine/turbo';\nimport { FSM, type AudioPort, type Phase, type PhaseContext } from '../engine/fsm';\nimport { defaultPhases } from '../engine/phases';\nimport { roundInfo, type InterpretBook } from '../engine/round';\nimport { bindMixerToHud, type MixerLike } from '../audio/bind';\nimport { bindInputSounds, type InputSoundMap } from '../audio/inputs';\nimport type { GameAudioOptions, SoundEntry } from '../audio';\nimport { createLoader, type GameLoader, type LoaderConfig } from '../loader';\nimport { modeCostOf, type GameConfig } from './config';\n\n/**\n * Declarative audio: hand the core your sounds and it lazily creates the `@schmooky/zvuk`\n * mixer (master → music/sfx/ambience buses + ducking), preloads them, and auto-binds it to\n * the HUD (sliders/mute + unlock). zvuk loads in its OWN async chunk — a game without sound\n * pays nothing. (Advanced: pass a ready `GameAudio` instance instead, for custom buses/FX.)\n */\nexport interface AudioSpec extends GameAudioOptions {\n sounds: SoundEntry[];\n /** Optional: play a cue on HUD input events (spin/bet/autoplay/turbo/toggle/skip). The\n * core auto-binds these after the HUD mounts — the named sounds must be in `sounds`. */\n inputSounds?: InputSoundMap;\n}\n\n/** Passed to `mountView` — everything the game's scene needs (not the round/fsm yet). */\nexport interface ViewContext {\n config: GameConfig;\n stores: RootStore;\n hud: BootedHud;\n ticker: Ticker;\n audio: AudioPort | null;\n /** Turbo speed + slam-stop (core-owned) — the scene may branch on `turbo.level`/`speed`. */\n turbo: TurboState;\n /** The boot loader (if `loader` was configured) — drive `setProgress` while your scene\n * loads art, and (with `loader.manual`) call `done()` when the scene is ready. `null`\n * when no loader is used. */\n loader: GameLoader | null;\n}\n\nexport interface CreateStakeGameOptions<T = unknown, V = unknown, E = unknown> {\n config: GameConfig;\n /** The game's ONE money seam: typed RGS round (`Round<E>`) → your model. Pure. */\n interpretBook: InterpretBook<T, E>;\n /** Mount the game's pixi scene/presenter/stores; returns the view handed to phases. */\n mountView: (host: HTMLElement, ctx: ViewContext) => V;\n /** The game's Present phase (+ any overrides); Idle/Spin/Settle are provided. */\n phases?: Phase<T, V, E>[];\n /** Sounds (declarative — the core builds + wires the mixer) OR a ready `GameAudio`/AudioPort. */\n audio?: AudioPort | AudioSpec | null;\n /** Override the transport (tests / a supplied mock). */\n network?: NetworkManager;\n /** Override launch params (tests / embedding). */\n runtime?: Partial<RuntimeConfig>;\n /** Host for the pixi HUD canvas (default document.body). */\n hudHost?: HTMLElement;\n /** Host for the game scene (default = hudHost). */\n sceneHost?: HTMLElement;\n /** Passthrough to `mountHud` (spinSkin, icons, gsap, menu:false, hooks, …). */\n hudOptions?: Record<string, unknown>;\n /**\n * A configurable boot loader — shows the instant boot starts, advances across the boot\n * milestones, then fills + pops away when the game is ready. Pass a {@link LoaderConfig}\n * to enable it (title defaults to `config.title`); omit or `false` to render none (the\n * game keeps its own HTML loader). With `manual: true`, call `ctx.loader.done()` yourself\n * (e.g. after the scene's art finishes loading).\n */\n loader?: LoaderConfig | false;\n}\n\n/**\n * Stake policy: buys/activations costing more than this many × base bet require a confirm\n * (no one-click). A buy-feature always costs far more than 2×, so in practice this means\n * \"always confirm a buy\". The jurisdiction (`auth.config.jurisdiction`) may override it; a\n * game never sets it — it's compliance, not design.\n */\nconst STAKE_CONFIRM_ABOVE_COST = 2;\n\n/** Fallback min-payout coefficient used ONLY when `config.minBet` is unset: the\n * currency-derived floor is `minUnit / MIN_PAYOUT_COEF` (= 5 minimal units at 0.2). */\nconst MIN_PAYOUT_COEF = 0.2;\n\n/** Persisted player UI preferences (sound mute + turbo mode). Best-effort — every access\n * is guarded: Safari Private Browsing, a cross-origin-iframe storage block, or a hardened\n * session throws on access, so this degrades to \"no persistence\", never into the boot. */\nconst PREFS_KEY = 'stakeplate.prefs';\ninterface GamePrefs {\n muted?: boolean;\n turbo?: number;\n}\nfunction readPrefs(): GamePrefs {\n try {\n return JSON.parse(localStorage.getItem(PREFS_KEY) || '{}') as GamePrefs;\n } catch {\n return {};\n }\n}\nfunction writePrefs(patch: GamePrefs): void {\n try {\n localStorage.setItem(PREFS_KEY, JSON.stringify({ ...readPrefs(), ...patch }));\n } catch {\n /* storage disabled / full — preferences just won't persist this session */\n }\n}\n\n/** Drop server bet-ladder levels below the minimum bet — the explicit `config.minBet`, else\n * the currency-derived floor (5 minimal units) — so the smallest win can't round below one\n * minimal unit. Never returns empty (keeps the largest level if every level is below). */\nfunction applyMinBet(levels: number[], minBet: number | undefined, decimals: number): number[] {\n const minUnit = 10 ** -decimals;\n const floor = minBet ?? minUnit / MIN_PAYOUT_COEF;\n const filtered = levels.filter((b) => b >= floor - minUnit / 1000);\n return filtered.length ? filtered : [Math.max(...levels)];\n}\n\n/** Snap a wanted bet up to the first ladder level that is >= it (or the closest). */\nfunction snapToLadder(levels: number[], want: number): number {\n if (levels.includes(want)) return want;\n return levels.find((b) => b >= want) ?? levels[levels.length - 1] ?? want;\n}\n\n/** Server/replay-sourced values that drive the HUD spec — never taken from the game config. */\ninterface HudSpecInput {\n currency: string;\n betLevels: number[]; // major units — the authoritative ladder\n defaultBet: number; // major units\n rtp: number; // display %\n confirmBuyAboveCost: number; // jurisdiction policy\n}\n\n/** A read-only snapshot of the running game — for the dev harness, tests and debugging. */\nexport interface GameSnapshot {\n phase: string;\n balance: number;\n bet: number;\n lastWin: number;\n currency: string;\n spinning: boolean;\n}\n\nexport interface StakeGame {\n start(): Promise<void>;\n dispose(): void;\n /** Current phase + store values. The declarative harness asserts on this (no screenshots). */\n inspect(): GameSnapshot;\n /** Trigger a spin the same way the HUD spin button does — only from Idle. For harness/tests. */\n requestSpin(): boolean;\n}\n\nexport function createStakeGame<T = unknown, V = unknown, E = unknown>(opts: CreateStakeGameOptions<T, V, E>): StakeGame {\n const runtime = readRuntime({ overrides: opts.runtime });\n const network = opts.network ?? createNetwork(runtime);\n const stores = new RootStore();\n const ticker = new RealTicker();\n const turbo = new TurboClock(opts.config.turboSpeeds); // core-owned turbo speed + slam-stop\n // A ready AudioPort instance is used as-is; an AudioSpec is resolved lazily in start()\n // (the core creates the zvuk mixer from its own async chunk) — see resolveAudio().\n let audio: AudioPort | null = opts.audio && !isAudioSpec(opts.audio) ? opts.audio : null;\n const app = new Application();\n const disposers: Array<() => void> = [];\n let hud: BootedHud | null = null;\n let fsm: FSM<T, V, E> | null = null;\n let loader: GameLoader | null = null;\n\n // The battery wires the library's DESIGNED default icon set (the white Figma coins +\n // rotating-arrows spin skin) so every game gets the intended HUD out of the box — not\n // the lib's no-art placeholder geometry. `loadBuiltinArt` pulls the whole /art bundle\n // once; a game overrides any icon or the spin skin via `hudOptions.icons`/`spinSkin`.\n let builtinArt: ReturnType<typeof loadBuiltinArt> | null = null;\n const hudOpts = async (): Promise<Record<string, unknown>> => {\n builtinArt ??= loadBuiltinArt();\n const art = await builtinArt;\n const user = (opts.hudOptions ?? {}) as { icons?: Record<string, unknown>; spinSkin?: unknown };\n return {\n spinSkin: art.spinSkin,\n ...user,\n icons: { ...(art.icons as Record<string, unknown>), ...(user.icons ?? {}) },\n };\n };\n\n // The HUD spec is driven by SERVER-authoritative values (the ladder + confirm policy come\n // from `authenticate`/jurisdiction, not the game). `buildSpec` just formats them.\n const buildSpec = (s: HudSpecInput, extra?: { hideBalance?: boolean }): Record<string, unknown> => ({\n currency: currencyFor(s.currency),\n betLadder: { levels: s.betLevels, index: Math.max(0, s.betLevels.indexOf(s.defaultBet)) },\n rtp: s.rtp,\n game: { name: opts.config.title, version: opts.config.version ?? '1.0.0' },\n // Stake owns fullscreen in its iframe — the game must not render its own. The bonus\n // (buy-feature) button shows only when a mode is a buy/boost card — it opens the feature list.\n // Replay mode has no wallet (it's a recorded round) — Stake approval: show NO balance\n // readout; the round's facts live in the replay panel instead.\n controls: {\n fullscreen: { hidden: true },\n ...(buyFeaturesOf(opts.config).length ? { bonus: { hidden: false } } : {}),\n ...(extra?.hideBalance ? { balance: { hidden: true } } : {}),\n },\n // Compliance: the buy-feature confirm threshold is jurisdiction policy, not a game knob.\n buyFeature: { confirmAboveCost: s.confirmBuyAboveCost },\n ...(opts.config.rules ? { menu: opts.config.rules } : {}),\n locale: {\n locale: runtime.language,\n messages: opts.config.messages ?? { en: {} },\n ...(opts.config.socialMessages ? { socialMessages: opts.config.socialMessages } : {}),\n },\n ...(opts.config.spec ?? {}),\n });\n\n const initApp = async (host: HTMLElement): Promise<void> => {\n await app.init({ resizeTo: window, backgroundAlpha: 0, antialias: true, resolution: Math.min(window.devicePixelRatio || 1, 2), autoDensity: true });\n host.appendChild(app.canvas);\n };\n\n // Build the mixer from a declarative AudioSpec. The `@stakeplate/core/audio` module (the\n // ONLY thing that pulls @schmooky/zvuk) is loaded via a DYNAMIC import, so it lands in its\n // own async chunk — an audio-less game never fetches zvuk. Sounds preload in the background.\n const resolveAudio = async (): Promise<void> => {\n if (!isAudioSpec(opts.audio)) return;\n const { createGameAudio } = await import('../audio');\n const mixer = createGameAudio(opts.audio);\n void mixer.load(opts.audio.sounds);\n audio = mixer;\n };\n\n async function start(): Promise<void> {\n const hudHost = opts.hudHost ?? document.body;\n const sceneHost = opts.sceneHost ?? hudHost;\n // Boot loader: paint it BEFORE anything heavy loads, then advance it across the milestones\n // below (init → auth → HUD → view) and fill + pop it away once the game is ready. `false`\n // (the default) renders none, so a game keeps whatever loader its HTML already has.\n if (opts.loader) loader = createLoader({ title: opts.config.title, ...opts.loader });\n const manualLoader = !!(opts.loader && opts.loader.manual);\n const phases = [...defaultPhases<T, V, E>(), ...(opts.phases ?? [])];\n const machine = (fsm = new FSM<T, V, E>(phases)); // outer `fsm` powers inspect()/requestSpin()\n loader?.setProgress(0.1);\n await resolveAudio(); // if `audio` is an AudioSpec, build the zvuk mixer (lazy chunk) + preload\n\n // ── REPLAY (Stake ?replay=true): fetch a round + play it back read-only ──────\n if (runtime.replay.active && network.replay) {\n try {\n await initApp(hudHost);\n loader?.setProgress(0.5);\n const cur = runtime.currency;\n const bet = runtime.replay.amount || 1; // replay carries its own bet; no ladder needed\n hud = mountHud(app, buildSpec({ currency: cur, betLevels: [bet], defaultBet: bet, rtp: opts.config.rtp ?? 96, confirmBuyAboveCost: STAKE_CONFIRM_ABOVE_COST }, { hideBalance: true }), (await hudOpts()) as never);\n stores.session.set({ currency: cur });\n stores.balance.setBalance(0);\n stores.balance.setBet(bet);\n if (runtime.social) hud.setSocial(true);\n loader?.setProgress(0.85);\n const view = opts.mountView(sceneHost, { config: opts.config, stores, hud, ticker, audio, turbo, loader });\n hud.setReplay(true);\n hud.lockInput();\n const cost = modeCostOf(opts.config, runtime.replay.mode);\n const raw = (await network.replay({ ...runtime.replay })) as Round<E>;\n const info = roundInfo(raw, bet, cost);\n const ctx = makeCtx(machine, view);\n ctx.round = { ...info, data: opts.interpretBook(raw, info), active: false, balance: 0, raw };\n const replayInfo = { baseBet: bet, costMultiplier: cost, payoutMultiplier: info.multiplier, amount: info.totalWin, currency: currencyFor(cur) };\n const playRound = async (): Promise<void> => {\n stores.ui.setSpinning(true);\n await machine.transition('present'); // game's Present plays it back → settle → idle\n hud!.replayEnd(replayInfo, () => void playRound());\n };\n hud.replayStart(replayInfo, () => void playRound());\n if (!manualLoader) void loader?.done();\n } catch (err) {\n loader?.remove();\n showBootError({ title: 'Cannot load the replay', message: 'The recorded round could not be loaded. Please reload to try again.', detail: errMsg(err) });\n }\n return;\n }\n\n // ── NORMAL BOOT ─────────────────────────────────────────────────────────────\n let auth;\n try {\n await initApp(hudHost);\n loader?.setProgress(0.35);\n auth = await network.authenticate();\n loader?.setProgress(0.6);\n } catch (err) {\n loader?.remove();\n showBootError({\n title: 'Cannot reach the game server',\n message: 'The game could not connect to or authenticate with the game server. Please reload to try again.',\n detail: errMsg(err),\n });\n return;\n }\n\n // Everything the HUD needs is SERVER-authoritative (from `authenticate`): the bet\n // ladder + default bet (per currency/jurisdiction), RTP, and the buy-confirm policy.\n const currency = auth.balance.currency;\n const juris = auth.config.jurisdiction ?? {};\n const rtp = auth.config.rtp ?? opts.config.rtp ?? 96;\n // Server ladder, trimmed by the client min-bet floor (explicit `config.minBet`, else the\n // currency-derived 5-minimal-units) so the smallest possible win can't round to sub-unit.\n const rawLevels = auth.config.betLevels.map((b) => b / API_AMOUNT_MULTIPLIER);\n const betLevels = applyMinBet(rawLevels, opts.config.minBet, currencyFor(currency).decimals ?? 2);\n const active = auth.round;\n const activeCost = active?.active ? modeCostOf(opts.config, active.mode) : 1;\n // Wanted default (restored from the active bet on resume), snapped up to the first legal\n // (floored) level so the ladder + store agree.\n const wantedDefault = active?.active\n ? active.amount / API_AMOUNT_MULTIPLIER / activeCost // resume: restore bet from the active amount\n : auth.config.defaultBetLevel / API_AMOUNT_MULTIPLIER;\n const defaultBet = snapToLadder(betLevels, wantedDefault);\n const confirmBuyAboveCost = juris.confirmBuyAboveCost ?? STAKE_CONFIRM_ABOVE_COST;\n\n hud = mountHud(app, buildSpec({ currency, betLevels, defaultBet, rtp, confirmBuyAboveCost }), (await hudOpts()) as never);\n hud.setCurrency(currencyFor(currency));\n hud.applyJurisdiction(juris);\n if (runtime.social || isSocialCurrency(currency)) hud.setSocial(true);\n\n stores.session.set({\n sessionId: runtime.sessionId,\n currency,\n rtp,\n availableBets: betLevels,\n jurisdiction: juris,\n });\n stores.balance.setBet(defaultBet);\n hud.setBet(defaultBet);\n loader?.setProgress(0.8);\n\n const view = opts.mountView(sceneHost, { config: opts.config, stores, hud, ticker, audio, turbo, loader });\n const ctx = makeCtx(machine, view);\n loader?.setProgress(0.95);\n\n // reactions store→HUD + HUD events\n disposers.push(reaction(() => stores.balance.balance, (b) => hud!.setBalance(b), { fireImmediately: false }));\n // The bet stepper is the base-bet source of truth. It emits `valueChanged` with\n // `id: 'bet-stepper'` and the ladder value (never the boosted display), so this feeds the\n // BASE bet even while an ante is active. (The lib mirrors it into `ui.bet`; the boost\n // reaction then overwrites the readout with the effective stake.)\n disposers.push(hud.on('valueChanged', (p) => { const v = p as { id?: string; value?: number }; if (v?.id === 'bet-stepper' && typeof v.value === 'number') stores.balance.setBet(v.value); }));\n // ── Spin triggers + turbo speed + autoplay (ALL core-owned) ─────────────────\n // One entry point for every spin: clears the slam-stop flag, then spins from idle.\n const beginSpin = (): void => {\n if (machine.current !== 'idle') return;\n turbo.resetSkip();\n void machine.transition('spin');\n };\n let holdActive = false; // press-and-hold turbo spin\n disposers.push(hud.on('spinRequested', beginSpin));\n // Autoplay/hold: the HUD owns the count picker, remaining count + RG limits (via\n // reportRound); the CORE runs the loop. Start on the first tick, then re-spin below.\n // Autoplay/hold both shorten game animations (turbo.setAutoplay) so long auto sessions\n // don't crawl. It's a floor on turbo speed — a faster turbo level still wins.\n disposers.push(hud.on('autoplayStarted', () => { turbo.setAutoplay(true); beginSpin(); }));\n disposers.push(hud.on('autoplayStopped', () => { turbo.setAutoplay(holdActive); }));\n disposers.push(hud.on('holdSpinStarted', () => { holdActive = true; turbo.setAutoplay(true); beginSpin(); }));\n disposers.push(hud.on('holdSpinStopped', () => { holdActive = false; turbo.setAutoplay(hud!.ui.autoplay.isActive); }));\n // Turbo speed (2-/3-mode cycler) + slam-stop (tap-to-skip the reels).\n disposers.push(hud.on('turboChanged', (p) => { const e = p as { index?: number }; if (typeof e.index === 'number') { turbo.setLevel(e.index); writePrefs({ turbo: e.index }); } }));\n disposers.push(hud.on('skipRequested', () => turbo.skip()));\n // Buy-feature: the bonus button opens the lib's feature-LIST modal (a card per buyable\n // mode). Two kinds of card, both routed through the jurisdiction confirm gate (no one-click\n // above the threshold):\n // • `buy` → one-shot. On confirm, spin that mode ONCE (SpinPhase charges its full cost).\n // • `boost` → a persistent ante. Activating sets it as the ACTIVE mode, so every following\n // base spin plays it at its full `cost×` (via `nextMode()`); toggling off restores base.\n // The modal owns opening/closing/confirm + single-activation; the core supplies the cards +\n // the onBuy/onActivate hooks and reflects the boosted stake in the bet readout.\n const features = buyFeaturesOf(opts.config);\n if (features.length) {\n // The bet readout shows the EFFECTIVE stake (base × the active ante's cost, emphasised)\n // while a boost is on, and the plain base bet otherwise — re-applied on bet/mode change.\n const applyBetDisplay = (): void => {\n const boost = stores.ui.activeMode;\n const cost = boost ? modeCostOf(opts.config, boost) : 1;\n hud!.ui.bet.set(stores.balance.bet * cost);\n (hud!.ui.bet as unknown as { setEmphasis?: (on: boolean) => void }).setEmphasis?.(cost !== 1);\n };\n disposers.push(\n mountBuyFeatureModal(app, hud, features, {\n activation: 'single', // one ante at a time (Stake)\n // Read the BASE bet straight from the stepper CONTROL (updated synchronously by\n // inc()/dec()) — NOT `stores.balance.bet`, which the core updates a tick later off\n // `valueChanged`, so the modal would lag one step (first +/- press \"doesn't\n // register\", +then- re-fires +). The readout still shows the boosted stake.\n getBet: () => hud!.ui.betStepper.value,\n onBuy: (id) => {\n if (machine.current !== 'idle') return;\n // Buying plays at the BASE bet — clear any active ante so its multiplied\n // (emphasised, yellow) bet readout reverts to the plain base bet first.\n if (stores.ui.activeMode) {\n stores.ui.setActiveMode(null);\n applyBetDisplay();\n }\n stores.ui.setOneShotMode(id);\n beginSpin();\n },\n onActivate: (ids) => {\n stores.ui.setActiveMode(ids[0] ?? null); // persistent ante; nextMode() prefers it\n applyBetDisplay();\n },\n }),\n );\n disposers.push(reaction(() => [stores.balance.bet, stores.ui.activeMode] as const, applyBetDisplay));\n }\n // Keep spinning while autoplay/hold is active: after each settled round (spinning\n // true → false), once idle, pause the (turbo-scaled) gap and spin again — stopping on\n // a blocking notice/error or when the next base stake is unaffordable.\n const autoplayGapMs = opts.config.autoplayGapMs ?? 250;\n disposers.push(\n reaction(\n () => stores.ui.spinning,\n (spinning, prev) => {\n if (prev !== true || spinning !== false) return;\n void turbo.delay(autoplayGapMs).then(() => {\n if (machine.current !== 'idle') return;\n if (!hud!.ui.autoplay.isActive && !holdActive) return;\n if (hud!.ui.noticeBlocks.get().length > 0 || stores.balance.balance < stores.balance.bet) {\n hud!.ui.autoplay.stop();\n holdActive = false;\n return;\n }\n beginSpin();\n });\n },\n ),\n );\n\n // Auto-wire audio ↔ HUD (Music/Effects sliders + mute, persisted) + unlock on the first\n // spin gesture — IF a mixer-like `audio` was provided. Structural check, no @schmooky/zvuk\n // import here, so audio-less games don't bundle it. (A game may bind manually instead.)\n const mixer = audio as unknown as MixerLike | null;\n if (mixer && typeof mixer.setGroupLevel === 'function') {\n disposers.push(bindMixerToHud(mixer, hud));\n if (typeof mixer.unlock === 'function') {\n const off = hud.on('spinRequested', () => { void mixer.unlock!(); off(); });\n disposers.push(off);\n }\n }\n // Input sounds: if the declarative AudioSpec named cues per HUD input, wire them now.\n if (audio && isAudioSpec(opts.audio) && opts.audio.inputSounds) {\n disposers.push(bindInputSounds(audio, hud, opts.audio.inputSounds));\n }\n\n // ── Persist player prefs (sound mute + turbo mode) across sessions ──────────\n // Restore BEFORE the game is interactive so the controls boot in the saved position\n // (setMuted also propagates to the audio mixer via bindMixerToHud's muted subscription\n // above). turboChanged below persists the turbo mode; anon/blocked-storage is safe.\n const prefs = readPrefs();\n if (typeof prefs.muted === 'boolean') hud.setMuted(prefs.muted);\n if (typeof prefs.turbo === 'number' && prefs.turbo > 0) {\n hud.ui.turbo.setIndex(prefs.turbo);\n turbo.setLevel(prefs.turbo);\n }\n disposers.push(hud.ui.muted.subscribe((m) => writePrefs({ muted: m })));\n\n // ── ACTIVE-ROUND RESUME: settle it, land idle, then a \"round in progress\" modal ──\n if (active?.active) {\n // The player refreshed while a round was still open. Settle it now (balance becomes\n // authoritative; the bet was restored into the ladder above), land on a clean idle\n // board, then show a NON-dismissible modal — on Continue we replay the recovered\n // round through the game's Present phase so the player watches how it resolved before\n // regaining control. (Not awaited — start() proceeds to fill/pop the loader; the modal\n // shows over the game once the loader fades.)\n const end = await network.endRound();\n const raw = active as Round<E>;\n const info = roundInfo(raw, defaultBet, activeCost);\n const settledBal = end.balance.amount / API_AMOUNT_MULTIPLIER;\n stores.balance.setBalance(settledBal);\n const resumeRound = { ...info, data: opts.interpretBook(raw, info), active: false, balance: settledBal, raw };\n await machine.transition('idle');\n hud.showFatal('You have an unfinished round. Continue to see how it ends.', {\n title: 'Round in progress',\n tone: 'info',\n actions: [\n {\n label: 'Continue',\n variant: 'primary',\n onSelect: () => {\n hud!.hideNotice();\n hud!.lockInput(); // no spins/buys while the recovered round replays\n ctx.round = resumeRound;\n void machine.transition('present').finally(() => hud!.unlockInput());\n },\n },\n ],\n });\n } else {\n stores.balance.setBalance(auth.balance.amount / API_AMOUNT_MULTIPLIER);\n await machine.transition('idle');\n }\n // The game is booted + interactive → fill + pop the loader away (unless the game asked\n // to drive it manually, e.g. to wait for its scene art via `ctx.loader.done()`).\n if (!manualLoader) void loader?.done();\n }\n\n function makeCtx(fsm: FSM<T, V, E>, view: V): PhaseContext<T, V, E> {\n const ctx: PhaseContext<T, V, E> = {\n config: opts.config,\n stores,\n network,\n hud: hud as BootedHud,\n ticker,\n view,\n audio,\n turbo,\n loader,\n interpretBook: opts.interpretBook,\n fsm,\n round: null,\n modeCost: (mode) => modeCostOf(opts.config, mode),\n };\n fsm.bind(ctx);\n return ctx;\n }\n\n function dispose(): void {\n for (const d of disposers.splice(0)) d();\n hud?.dispose();\n app.destroy(true);\n }\n\n function inspect(): GameSnapshot {\n return {\n phase: fsm?.current ?? 'boot',\n balance: stores.balance.balance,\n bet: stores.balance.bet,\n lastWin: stores.balance.lastWin,\n currency: stores.session.currency,\n spinning: stores.ui.spinning,\n };\n }\n\n function requestSpin(): boolean {\n if (!fsm || fsm.current !== 'idle') return false;\n void fsm.transition('spin');\n return true;\n }\n\n return { start, dispose, inspect, requestSpin };\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** An AudioSpec (declarative sounds) vs a ready AudioPort/GameAudio instance. */\nfunction isAudioSpec(a: unknown): a is AudioSpec {\n return !!a && typeof a === 'object' && Array.isArray((a as AudioSpec).sounds);\n}\n\nconst capitalize = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);\n\n/** The buyable/activatable modes (`modes.<key>.buy` / `.boost`) as buy-feature cards — the\n * list the bonus button opens. A plain number mode or one without `buy`/`boost` is not a card.\n * `cost` in config is the FULL play multiplier (× bet); a boost card shows the SURCHARGE it\n * adds over a base spin (`cost − 1`, e.g. a 2× ante → `+1× bet`), so the card + confirm read\n * right while the mode is still charged its full `cost` when it spins. */\nfunction buyFeaturesOf(config: GameConfig): FeatureSpec[] {\n const out: FeatureSpec[] = [];\n for (const [key, m] of Object.entries(config.modes ?? {})) {\n if (!m || typeof m !== 'object' || !(m.buy || m.boost)) continue;\n const variant = m.boost ? 'boost' : 'buy';\n out.push({ id: key, name: m.name ?? capitalize(key), variant, cost: variant === 'boost' ? m.cost - 1 : m.cost, image: m.image });\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,13 @@ interface ReplayLaunch {
|
|
|
17
17
|
}
|
|
18
18
|
interface RuntimeConfig {
|
|
19
19
|
rgsUrl: string;
|
|
20
|
+
/**
|
|
21
|
+
* True when a real `rgs_url`/`rgsUrl` launch param was present (vs falling back to the
|
|
22
|
+
* local default). A provided host means "talk to it" — this is what tells the transport
|
|
23
|
+
* picker to use the REAL Stake RGS, even for a `demo` fun-play launch. Set it in
|
|
24
|
+
* `overrides` to force the real transport when you supply `rgsUrl` programmatically.
|
|
25
|
+
*/
|
|
26
|
+
rgsUrlProvided: boolean;
|
|
20
27
|
sessionId: string;
|
|
21
28
|
/** BCP-47-ish language for `/wallet/authenticate` + the HUD locale ('br' → 'pt'). */
|
|
22
29
|
language: string;
|
|
@@ -26,8 +33,18 @@ interface RuntimeConfig {
|
|
|
26
33
|
social: boolean;
|
|
27
34
|
/** `?device=` — Stake's viewport hint ('' | 'desktop' | 'mobile' | a small popout). */
|
|
28
35
|
device: string;
|
|
29
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* `?demo=true` — Stake FUN-PLAY: a demo (non-cash) wallet on the REAL RGS. This is NOT a
|
|
38
|
+
* "use a fake backend" switch — Stake's own local-redirect dev tool launches
|
|
39
|
+
* `…?rgs_url=rgsd.stake-engine.com&sessionID=…&demo=true`, and that session still
|
|
40
|
+
* authenticates + plays against the real RGS. To run the in-process mock, use `?mock=true`.
|
|
41
|
+
*/
|
|
30
42
|
demo: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* `?mock=true` — force the built-in in-process mock RGS (no backend). For pure client dev
|
|
45
|
+
* without a real session; distinct from Stake's `demo` fun-play. Overrides `rgs_url`.
|
|
46
|
+
*/
|
|
47
|
+
mock: boolean;
|
|
31
48
|
replay: ReplayLaunch;
|
|
32
49
|
}
|
|
33
50
|
type ParamSource = (name: string) => string | undefined;
|
|
@@ -43,6 +60,16 @@ declare function readRuntime(opts?: {
|
|
|
43
60
|
overrides?: Partial<RuntimeConfig>;
|
|
44
61
|
defaultRgsUrl?: string;
|
|
45
62
|
}): RuntimeConfig;
|
|
63
|
+
/**
|
|
64
|
+
* True when the game was launched by the Stake platform — i.e. a real `rgs_url` was provided
|
|
65
|
+
* (the dev dashboard's "local redirect", or a live launch) and the in-process mock wasn't
|
|
66
|
+
* forced with `?mock=true`. This holds for `demo=true` fun-play launches too: they still run
|
|
67
|
+
* against the real RGS.
|
|
68
|
+
*
|
|
69
|
+
* A game gates its local dev mock on `!isStakeLaunch()` — so bare `pnpm dev` uses the mock,
|
|
70
|
+
* while a Stake launch auto-connects to the real RGS with the launch `sessionID`, no code edit.
|
|
71
|
+
*/
|
|
72
|
+
declare function isStakeLaunch(runtime?: RuntimeConfig): boolean;
|
|
46
73
|
//#endregion
|
|
47
74
|
//#region src/rgs/MockNetworkManager.d.ts
|
|
48
75
|
interface MockOptions {
|
|
@@ -111,10 +138,22 @@ interface NetworkManager {
|
|
|
111
138
|
dispose?(): void;
|
|
112
139
|
}
|
|
113
140
|
/**
|
|
114
|
-
* Pick the transport from the runtime:
|
|
115
|
-
*
|
|
141
|
+
* Pick the transport from the launch runtime:
|
|
142
|
+
*
|
|
143
|
+
* - a supplied `mock` instance, or `?mock=true` → the in-process mock RGS (no backend)
|
|
144
|
+
* - a real `rgs_url` was provided → the REAL Stake RGS. This INCLUDES a
|
|
145
|
+
* `?demo=true` fun-play launch: a Stake demo session still authenticates + plays
|
|
146
|
+
* against `rgs_url`, so `demo` must NOT force the mock.
|
|
147
|
+
* - `?demo=true` with NO host → the mock (bare local click-around)
|
|
148
|
+
* - otherwise → the real transport at the default
|
|
149
|
+
* (local emulator) host
|
|
150
|
+
*
|
|
151
|
+
* This is the key fix over the old `demo → mock`: Stake's own local-redirect dev tool opens
|
|
152
|
+
* `…?rgs_url=rgsd.stake-engine.com&sessionID=…&demo=true`. Under the old rule that demo
|
|
153
|
+
* session was hijacked by the in-process mock and never reached the RGS; now it authorizes
|
|
154
|
+
* on Stake, pulls the real balance/config, and spins/buys with real requests.
|
|
116
155
|
*/
|
|
117
156
|
declare function createNetwork(runtime: RuntimeConfig, mock?: MockNetworkManager): NetworkManager;
|
|
118
157
|
//#endregion
|
|
119
|
-
export { MockOptions as a, RuntimeConfig as c, MockNetworkManager as i,
|
|
120
|
-
//# sourceMappingURL=network-
|
|
158
|
+
export { MockOptions as a, RuntimeConfig as c, urlParam as d, MockNetworkManager as i, isStakeLaunch as l, PlayArgs as n, ScriptedRound as o, createNetwork as r, ReplayLaunch as s, NetworkManager as t, readRuntime as u };
|
|
159
|
+
//# sourceMappingURL=network-CBxKQzeb.d.ts.map
|
package/dist/rgs.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as Balance, c as JurisdictionConfig, d as ReplayParams, f as RgsConfig, h as roundEvents, i as BOOK_AMOUNT_MULTIPLIER, l as PlayRequest, m as Round, n as AuthenticateRequest, o as EndRoundRequest, p as RgsError, r as AuthenticateResponse, s as EndRoundResponse, t as API_AMOUNT_MULTIPLIER, u as PlayResponse } from "./protocol-C1KamF3t.js";
|
|
2
|
-
import { a as MockOptions, c as RuntimeConfig, i as MockNetworkManager, l as
|
|
2
|
+
import { a as MockOptions, c as RuntimeConfig, d as urlParam, i as MockNetworkManager, l as isStakeLaunch, n as PlayArgs, o as ScriptedRound, r as createNetwork, s as ReplayLaunch, t as NetworkManager, u as readRuntime } from "./network-CBxKQzeb.js";
|
|
3
3
|
//#region src/rgs/StakeNetworkManager.d.ts
|
|
4
4
|
interface StakeNetworkOptions {
|
|
5
5
|
rgsUrl: string;
|
|
@@ -21,5 +21,5 @@ declare class StakeNetworkManager implements NetworkManager {
|
|
|
21
21
|
private get;
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
|
-
export { API_AMOUNT_MULTIPLIER, AuthenticateRequest, AuthenticateResponse, BOOK_AMOUNT_MULTIPLIER, Balance, EndRoundRequest, EndRoundResponse, JurisdictionConfig, MockNetworkManager, MockOptions, NetworkManager, PlayArgs, PlayRequest, PlayResponse, ReplayLaunch, ReplayParams, RgsConfig, RgsError, Round, RuntimeConfig, ScriptedRound, StakeNetworkManager, StakeNetworkOptions, createNetwork, readRuntime, roundEvents, urlParam };
|
|
24
|
+
export { API_AMOUNT_MULTIPLIER, AuthenticateRequest, AuthenticateResponse, BOOK_AMOUNT_MULTIPLIER, Balance, EndRoundRequest, EndRoundResponse, JurisdictionConfig, MockNetworkManager, MockOptions, NetworkManager, PlayArgs, PlayRequest, PlayResponse, ReplayLaunch, ReplayParams, RgsConfig, RgsError, Round, RuntimeConfig, ScriptedRound, StakeNetworkManager, StakeNetworkOptions, createNetwork, isStakeLaunch, readRuntime, roundEvents, urlParam };
|
|
25
25
|
//# sourceMappingURL=rgs.d.ts.map
|
package/dist/rgs.js
CHANGED
|
@@ -14,14 +14,17 @@ function readRuntime(opts = {}) {
|
|
|
14
14
|
const param = opts.param ?? urlParam;
|
|
15
15
|
const rawLang = param("lang");
|
|
16
16
|
const language = rawLang === "br" ? "pt" : rawLang || "en";
|
|
17
|
+
const rgsUrlParam = param("rgs_url") ?? param("rgsUrl");
|
|
17
18
|
const base = {
|
|
18
|
-
rgsUrl:
|
|
19
|
+
rgsUrl: rgsUrlParam ?? opts.defaultRgsUrl ?? "http://localhost:4758",
|
|
20
|
+
rgsUrlProvided: rgsUrlParam != null,
|
|
19
21
|
sessionId: param("sessionID") ?? param("sessionId") ?? "dev",
|
|
20
22
|
language,
|
|
21
23
|
currency: param("currency") ?? "USD",
|
|
22
24
|
social: param("social") === "true",
|
|
23
25
|
device: param("device") ?? "",
|
|
24
26
|
demo: param("demo") === "true",
|
|
27
|
+
mock: param("mock") === "true",
|
|
25
28
|
replay: {
|
|
26
29
|
active: param("replay") === "true",
|
|
27
30
|
amount: Number(param("amount")) || 0,
|
|
@@ -40,6 +43,18 @@ function readRuntime(opts = {}) {
|
|
|
40
43
|
}
|
|
41
44
|
};
|
|
42
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* True when the game was launched by the Stake platform — i.e. a real `rgs_url` was provided
|
|
48
|
+
* (the dev dashboard's "local redirect", or a live launch) and the in-process mock wasn't
|
|
49
|
+
* forced with `?mock=true`. This holds for `demo=true` fun-play launches too: they still run
|
|
50
|
+
* against the real RGS.
|
|
51
|
+
*
|
|
52
|
+
* A game gates its local dev mock on `!isStakeLaunch()` — so bare `pnpm dev` uses the mock,
|
|
53
|
+
* while a Stake launch auto-connects to the real RGS with the launch `sessionID`, no code edit.
|
|
54
|
+
*/
|
|
55
|
+
function isStakeLaunch(runtime = readRuntime()) {
|
|
56
|
+
return runtime.rgsUrlProvided && !runtime.mock;
|
|
57
|
+
}
|
|
43
58
|
//#endregion
|
|
44
59
|
//#region src/rgs/StakeNetworkManager.ts
|
|
45
60
|
var StakeNetworkManager = class {
|
|
@@ -102,11 +117,29 @@ var StakeNetworkManager = class {
|
|
|
102
117
|
//#endregion
|
|
103
118
|
//#region src/rgs/network.ts
|
|
104
119
|
/**
|
|
105
|
-
* Pick the transport from the runtime:
|
|
106
|
-
*
|
|
120
|
+
* Pick the transport from the launch runtime:
|
|
121
|
+
*
|
|
122
|
+
* - a supplied `mock` instance, or `?mock=true` → the in-process mock RGS (no backend)
|
|
123
|
+
* - a real `rgs_url` was provided → the REAL Stake RGS. This INCLUDES a
|
|
124
|
+
* `?demo=true` fun-play launch: a Stake demo session still authenticates + plays
|
|
125
|
+
* against `rgs_url`, so `demo` must NOT force the mock.
|
|
126
|
+
* - `?demo=true` with NO host → the mock (bare local click-around)
|
|
127
|
+
* - otherwise → the real transport at the default
|
|
128
|
+
* (local emulator) host
|
|
129
|
+
*
|
|
130
|
+
* This is the key fix over the old `demo → mock`: Stake's own local-redirect dev tool opens
|
|
131
|
+
* `…?rgs_url=rgsd.stake-engine.com&sessionID=…&demo=true`. Under the old rule that demo
|
|
132
|
+
* session was hijacked by the in-process mock and never reached the RGS; now it authorizes
|
|
133
|
+
* on Stake, pulls the real balance/config, and spins/buys with real requests.
|
|
107
134
|
*/
|
|
108
135
|
function createNetwork(runtime, mock) {
|
|
109
|
-
if (
|
|
136
|
+
if (mock) return mock;
|
|
137
|
+
if (runtime.mock) return new MockNetworkManager();
|
|
138
|
+
if (runtime.rgsUrlProvided) return stakeTransport(runtime);
|
|
139
|
+
if (runtime.demo) return new MockNetworkManager();
|
|
140
|
+
return stakeTransport(runtime);
|
|
141
|
+
}
|
|
142
|
+
function stakeTransport(runtime) {
|
|
110
143
|
return new StakeNetworkManager({
|
|
111
144
|
rgsUrl: runtime.rgsUrl,
|
|
112
145
|
sessionId: runtime.sessionId,
|
|
@@ -114,6 +147,6 @@ function createNetwork(runtime, mock) {
|
|
|
114
147
|
});
|
|
115
148
|
}
|
|
116
149
|
//#endregion
|
|
117
|
-
export { API_AMOUNT_MULTIPLIER, BOOK_AMOUNT_MULTIPLIER, MockNetworkManager, StakeNetworkManager, createNetwork, readRuntime, roundEvents, urlParam };
|
|
150
|
+
export { API_AMOUNT_MULTIPLIER, BOOK_AMOUNT_MULTIPLIER, MockNetworkManager, StakeNetworkManager, createNetwork, isStakeLaunch, readRuntime, roundEvents, urlParam };
|
|
118
151
|
|
|
119
152
|
//# sourceMappingURL=rgs.js.map
|
package/dist/rgs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rgs.js","names":[],"sources":["../src/rgs/runtime.ts","../src/rgs/StakeNetworkManager.ts","../src/rgs/network.ts"],"sourcesContent":["/**\n * Stake launch parameters (mirrors `StakeEngine/web-sdk`\n * `packages/state-shared/src/stateUrl`): sessionID, rgs_url, lang, currency, device,\n * social, demo — plus the REPLAY set: replay, amount, game, version, mode, event.\n * `lang` is REQUIRED by `/wallet/authenticate` (its absence 400s the RGS).\n */\n\nexport interface ReplayLaunch {\n /** `?replay=true` — launch into replay (fetch + play back a recorded round). */\n active: boolean;\n amount: number;\n game: string;\n version: string;\n mode: string;\n event: string;\n}\n\nexport interface RuntimeConfig {\n rgsUrl: string;\n sessionId: string;\n /** BCP-47-ish language for `/wallet/authenticate` + the HUD locale ('br' → 'pt'). */\n language: string;\n /** `?currency=` — currency code (used for replay, where there's no wallet to read it). */\n currency: string;\n /** `?social=true` — force Stake US social/sweepstakes wording. */\n social: boolean;\n /** `?device=` — Stake's viewport hint ('' | 'desktop' | 'mobile' | a small popout). */\n device: string;\n /** `?demo=true` — run against the built-in mock RGS, no real backend. */\n demo: boolean;\n replay: ReplayLaunch;\n}\n\ntype ParamSource = (name: string) => string | undefined;\n\n/** Read a URL param from `window.location.search` (undefined off-DOM). */\nexport function urlParam(name: string): string | undefined {\n if (typeof window === 'undefined') return undefined;\n return new URLSearchParams(window.location.search).get(name) ?? undefined;\n}\n\n/**\n * Parse the Stake launch params into a {@link RuntimeConfig}. Pass `overrides` (or a\n * custom `param` reader) for tests/embedding; otherwise reads the page URL. `rgsUrl`\n * defaults to a local emulator so `pnpm dev` works out of the box.\n */\nexport function readRuntime(opts: { param?: ParamSource; overrides?: Partial<RuntimeConfig>; defaultRgsUrl?: string } = {}): RuntimeConfig {\n const param = opts.param ?? urlParam;\n const rawLang = param('lang');\n const language = rawLang === 'br' ? 'pt' : rawLang || 'en';\n const base: RuntimeConfig = {\n rgsUrl: param('rgs_url') ?? param('rgsUrl') ?? opts.defaultRgsUrl ?? 'http://localhost:4758',\n sessionId: param('sessionID') ?? param('sessionId') ?? 'dev',\n language,\n currency: param('currency') ?? 'USD',\n social: param('social') === 'true',\n device: param('device') ?? '',\n demo: param('demo') === 'true',\n replay: {\n active: param('replay') === 'true',\n amount: Number(param('amount')) || 0,\n game: param('game') ?? '',\n version: param('version') ?? '',\n mode: param('mode') ?? '',\n event: param('event') ?? '',\n },\n };\n return { ...base, ...opts.overrides, replay: { ...base.replay, ...opts.overrides?.replay } };\n}\n","// StakeNetworkManager — the ONLY thing that speaks the Stake RGS wire. Thin: it\n// returns RAW protocol shapes; the engine (SpinPhase/boot) owns the end-round settle\n// + active-round resume. Money: RGS = API units (×1e6); the game's book = BOOK units.\n\nimport {\n API_AMOUNT_MULTIPLIER,\n type AuthenticateResponse,\n type EndRoundResponse,\n type PlayResponse,\n type ReplayParams,\n type Round,\n} from './protocol';\nimport type { NetworkManager, PlayArgs } from './network';\n\nexport interface StakeNetworkOptions {\n rgsUrl: string;\n sessionId: string;\n /** REQUIRED — `/wallet/authenticate` 400s without it. */\n language: string;\n}\n\nexport class StakeNetworkManager implements NetworkManager {\n private readonly base: string;\n /** Currency learned at authenticate; sent on every `/wallet/play`. */\n private currency = 'USD';\n\n constructor(private readonly opts: StakeNetworkOptions) {\n this.base = /^https?:\\/\\//.test(opts.rgsUrl) ? opts.rgsUrl : `https://${opts.rgsUrl}`;\n }\n\n async authenticate(): Promise<AuthenticateResponse> {\n const res = await this.post<AuthenticateResponse>('/wallet/authenticate', {\n sessionID: this.opts.sessionId,\n language: this.opts.language,\n });\n this.currency = res.balance.currency;\n return res;\n }\n\n async play(args: PlayArgs): Promise<PlayResponse> {\n return this.post<PlayResponse>('/wallet/play', {\n sessionID: this.opts.sessionId,\n currency: args.currency ?? this.currency,\n amount: Math.round(args.bet * API_AMOUNT_MULTIPLIER),\n mode: args.mode,\n });\n }\n\n async endRound(): Promise<EndRoundResponse> {\n return this.post<EndRoundResponse>('/wallet/end-round', { sessionID: this.opts.sessionId });\n }\n\n async replay(p: ReplayParams): Promise<Round> {\n const path = `/bet/replay/${encodeURIComponent(p.game)}/${encodeURIComponent(p.version)}/${encodeURIComponent(p.mode)}/${encodeURIComponent(p.event)}`;\n const res = await this.get<Record<string, unknown>>(path);\n // The RGS may return `{ round }` or the round object directly.\n return (res.round ?? res) as Round;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(this.base + path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`[StakeNetworkManager] ${path} -> ${res.status} ${text}`);\n }\n return (await res.json()) as T;\n }\n\n private async get<T>(path: string): Promise<T> {\n const res = await fetch(this.base + path, { method: 'GET', headers: { accept: 'application/json' } });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`[StakeNetworkManager] ${path} -> ${res.status} ${text}`);\n }\n return (await res.json()) as T;\n }\n}\n","// The transport seam. Adapters return RAW protocol shapes; the engine applies the\n// game's `interpretBook` and owns the round lifecycle (settle / resume).\n\nimport type { AuthenticateResponse, EndRoundResponse, PlayResponse, ReplayParams, Round } from './protocol';\nimport type { RuntimeConfig } from './runtime';\nimport { StakeNetworkManager } from './StakeNetworkManager';\nimport { MockNetworkManager } from './MockNetworkManager';\n\nexport interface PlayArgs {\n /** Base bet in MAJOR units (the transport converts to API units). */\n bet: number;\n /** Mode key — base / a boost / a bonus buy. The platform applies its cost. */\n mode: string;\n /** Currency code; defaults to the one learned at authenticate. */\n currency?: string;\n}\n\nexport interface NetworkManager {\n authenticate(): Promise<AuthenticateResponse>;\n play(args: PlayArgs): Promise<PlayResponse>;\n endRound(): Promise<EndRoundResponse>;\n /** Optional — fetch a recorded round for read-only replay. */\n replay?(params: ReplayParams): Promise<Round>;\n dispose?(): void;\n}\n\n/**\n * Pick the transport from the runtime: `?demo=true` (or a supplied `mock`) → the\n * in-process mock RGS; otherwise the real Stake transport.\n */\nexport function createNetwork(runtime: RuntimeConfig, mock?: MockNetworkManager): NetworkManager {\n if (runtime.demo || mock) return mock ?? new MockNetworkManager();\n return new StakeNetworkManager({\n rgsUrl: runtime.rgsUrl,\n sessionId: runtime.sessionId,\n language: runtime.language,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rgs.js","names":[],"sources":["../src/rgs/runtime.ts","../src/rgs/StakeNetworkManager.ts","../src/rgs/network.ts"],"sourcesContent":["/**\n * Stake launch parameters (mirrors `StakeEngine/web-sdk`\n * `packages/state-shared/src/stateUrl`): sessionID, rgs_url, lang, currency, device,\n * social, demo — plus the REPLAY set: replay, amount, game, version, mode, event.\n * `lang` is REQUIRED by `/wallet/authenticate` (its absence 400s the RGS).\n */\n\nexport interface ReplayLaunch {\n /** `?replay=true` — launch into replay (fetch + play back a recorded round). */\n active: boolean;\n amount: number;\n game: string;\n version: string;\n mode: string;\n event: string;\n}\n\nexport interface RuntimeConfig {\n rgsUrl: string;\n /**\n * True when a real `rgs_url`/`rgsUrl` launch param was present (vs falling back to the\n * local default). A provided host means \"talk to it\" — this is what tells the transport\n * picker to use the REAL Stake RGS, even for a `demo` fun-play launch. Set it in\n * `overrides` to force the real transport when you supply `rgsUrl` programmatically.\n */\n rgsUrlProvided: boolean;\n sessionId: string;\n /** BCP-47-ish language for `/wallet/authenticate` + the HUD locale ('br' → 'pt'). */\n language: string;\n /** `?currency=` — currency code (used for replay, where there's no wallet to read it). */\n currency: string;\n /** `?social=true` — force Stake US social/sweepstakes wording. */\n social: boolean;\n /** `?device=` — Stake's viewport hint ('' | 'desktop' | 'mobile' | a small popout). */\n device: string;\n /**\n * `?demo=true` — Stake FUN-PLAY: a demo (non-cash) wallet on the REAL RGS. This is NOT a\n * \"use a fake backend\" switch — Stake's own local-redirect dev tool launches\n * `…?rgs_url=rgsd.stake-engine.com&sessionID=…&demo=true`, and that session still\n * authenticates + plays against the real RGS. To run the in-process mock, use `?mock=true`.\n */\n demo: boolean;\n /**\n * `?mock=true` — force the built-in in-process mock RGS (no backend). For pure client dev\n * without a real session; distinct from Stake's `demo` fun-play. Overrides `rgs_url`.\n */\n mock: boolean;\n replay: ReplayLaunch;\n}\n\ntype ParamSource = (name: string) => string | undefined;\n\n/** Read a URL param from `window.location.search` (undefined off-DOM). */\nexport function urlParam(name: string): string | undefined {\n if (typeof window === 'undefined') return undefined;\n return new URLSearchParams(window.location.search).get(name) ?? undefined;\n}\n\n/**\n * Parse the Stake launch params into a {@link RuntimeConfig}. Pass `overrides` (or a\n * custom `param` reader) for tests/embedding; otherwise reads the page URL. `rgsUrl`\n * defaults to a local emulator so `pnpm dev` works out of the box.\n */\nexport function readRuntime(opts: { param?: ParamSource; overrides?: Partial<RuntimeConfig>; defaultRgsUrl?: string } = {}): RuntimeConfig {\n const param = opts.param ?? urlParam;\n const rawLang = param('lang');\n const language = rawLang === 'br' ? 'pt' : rawLang || 'en';\n const rgsUrlParam = param('rgs_url') ?? param('rgsUrl');\n const base: RuntimeConfig = {\n rgsUrl: rgsUrlParam ?? opts.defaultRgsUrl ?? 'http://localhost:4758',\n rgsUrlProvided: rgsUrlParam != null,\n sessionId: param('sessionID') ?? param('sessionId') ?? 'dev',\n language,\n currency: param('currency') ?? 'USD',\n social: param('social') === 'true',\n device: param('device') ?? '',\n demo: param('demo') === 'true',\n mock: param('mock') === 'true',\n replay: {\n active: param('replay') === 'true',\n amount: Number(param('amount')) || 0,\n game: param('game') ?? '',\n version: param('version') ?? '',\n mode: param('mode') ?? '',\n event: param('event') ?? '',\n },\n };\n return { ...base, ...opts.overrides, replay: { ...base.replay, ...opts.overrides?.replay } };\n}\n\n/**\n * True when the game was launched by the Stake platform — i.e. a real `rgs_url` was provided\n * (the dev dashboard's \"local redirect\", or a live launch) and the in-process mock wasn't\n * forced with `?mock=true`. This holds for `demo=true` fun-play launches too: they still run\n * against the real RGS.\n *\n * A game gates its local dev mock on `!isStakeLaunch()` — so bare `pnpm dev` uses the mock,\n * while a Stake launch auto-connects to the real RGS with the launch `sessionID`, no code edit.\n */\nexport function isStakeLaunch(runtime: RuntimeConfig = readRuntime()): boolean {\n return runtime.rgsUrlProvided && !runtime.mock;\n}\n","// StakeNetworkManager — the ONLY thing that speaks the Stake RGS wire. Thin: it\n// returns RAW protocol shapes; the engine (SpinPhase/boot) owns the end-round settle\n// + active-round resume. Money: RGS = API units (×1e6); the game's book = BOOK units.\n\nimport {\n API_AMOUNT_MULTIPLIER,\n type AuthenticateResponse,\n type EndRoundResponse,\n type PlayResponse,\n type ReplayParams,\n type Round,\n} from './protocol';\nimport type { NetworkManager, PlayArgs } from './network';\n\nexport interface StakeNetworkOptions {\n rgsUrl: string;\n sessionId: string;\n /** REQUIRED — `/wallet/authenticate` 400s without it. */\n language: string;\n}\n\nexport class StakeNetworkManager implements NetworkManager {\n private readonly base: string;\n /** Currency learned at authenticate; sent on every `/wallet/play`. */\n private currency = 'USD';\n\n constructor(private readonly opts: StakeNetworkOptions) {\n this.base = /^https?:\\/\\//.test(opts.rgsUrl) ? opts.rgsUrl : `https://${opts.rgsUrl}`;\n }\n\n async authenticate(): Promise<AuthenticateResponse> {\n const res = await this.post<AuthenticateResponse>('/wallet/authenticate', {\n sessionID: this.opts.sessionId,\n language: this.opts.language,\n });\n this.currency = res.balance.currency;\n return res;\n }\n\n async play(args: PlayArgs): Promise<PlayResponse> {\n return this.post<PlayResponse>('/wallet/play', {\n sessionID: this.opts.sessionId,\n currency: args.currency ?? this.currency,\n amount: Math.round(args.bet * API_AMOUNT_MULTIPLIER),\n mode: args.mode,\n });\n }\n\n async endRound(): Promise<EndRoundResponse> {\n return this.post<EndRoundResponse>('/wallet/end-round', { sessionID: this.opts.sessionId });\n }\n\n async replay(p: ReplayParams): Promise<Round> {\n const path = `/bet/replay/${encodeURIComponent(p.game)}/${encodeURIComponent(p.version)}/${encodeURIComponent(p.mode)}/${encodeURIComponent(p.event)}`;\n const res = await this.get<Record<string, unknown>>(path);\n // The RGS may return `{ round }` or the round object directly.\n return (res.round ?? res) as Round;\n }\n\n private async post<T>(path: string, body: unknown): Promise<T> {\n const res = await fetch(this.base + path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`[StakeNetworkManager] ${path} -> ${res.status} ${text}`);\n }\n return (await res.json()) as T;\n }\n\n private async get<T>(path: string): Promise<T> {\n const res = await fetch(this.base + path, { method: 'GET', headers: { accept: 'application/json' } });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`[StakeNetworkManager] ${path} -> ${res.status} ${text}`);\n }\n return (await res.json()) as T;\n }\n}\n","// The transport seam. Adapters return RAW protocol shapes; the engine applies the\n// game's `interpretBook` and owns the round lifecycle (settle / resume).\n\nimport type { AuthenticateResponse, EndRoundResponse, PlayResponse, ReplayParams, Round } from './protocol';\nimport type { RuntimeConfig } from './runtime';\nimport { StakeNetworkManager } from './StakeNetworkManager';\nimport { MockNetworkManager } from './MockNetworkManager';\n\nexport interface PlayArgs {\n /** Base bet in MAJOR units (the transport converts to API units). */\n bet: number;\n /** Mode key — base / a boost / a bonus buy. The platform applies its cost. */\n mode: string;\n /** Currency code; defaults to the one learned at authenticate. */\n currency?: string;\n}\n\nexport interface NetworkManager {\n authenticate(): Promise<AuthenticateResponse>;\n play(args: PlayArgs): Promise<PlayResponse>;\n endRound(): Promise<EndRoundResponse>;\n /** Optional — fetch a recorded round for read-only replay. */\n replay?(params: ReplayParams): Promise<Round>;\n dispose?(): void;\n}\n\n/**\n * Pick the transport from the launch runtime:\n *\n * - a supplied `mock` instance, or `?mock=true` → the in-process mock RGS (no backend)\n * - a real `rgs_url` was provided → the REAL Stake RGS. This INCLUDES a\n * `?demo=true` fun-play launch: a Stake demo session still authenticates + plays\n * against `rgs_url`, so `demo` must NOT force the mock.\n * - `?demo=true` with NO host → the mock (bare local click-around)\n * - otherwise → the real transport at the default\n * (local emulator) host\n *\n * This is the key fix over the old `demo → mock`: Stake's own local-redirect dev tool opens\n * `…?rgs_url=rgsd.stake-engine.com&sessionID=…&demo=true`. Under the old rule that demo\n * session was hijacked by the in-process mock and never reached the RGS; now it authorizes\n * on Stake, pulls the real balance/config, and spins/buys with real requests.\n */\nexport function createNetwork(runtime: RuntimeConfig, mock?: MockNetworkManager): NetworkManager {\n if (mock) return mock;\n if (runtime.mock) return new MockNetworkManager();\n if (runtime.rgsUrlProvided) return stakeTransport(runtime);\n if (runtime.demo) return new MockNetworkManager();\n return stakeTransport(runtime);\n}\n\nfunction stakeTransport(runtime: RuntimeConfig): StakeNetworkManager {\n return new StakeNetworkManager({\n rgsUrl: runtime.rgsUrl,\n sessionId: runtime.sessionId,\n language: runtime.language,\n });\n}\n"],"mappings":"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"}
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MockOptions, i as MockNetworkManager, o as ScriptedRound } from "./network-
|
|
1
|
+
import { a as MockOptions, i as MockNetworkManager, o as ScriptedRound } from "./network-CBxKQzeb.js";
|
|
2
2
|
import { r as InstantTicker, t as HudPort } from "./hud-port-DNfotn4U.js";
|
|
3
3
|
//#region src/testing/index.d.ts
|
|
4
4
|
/** A recording no-op HudPort — inspect `.calls` in tests. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stakeplate/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Batteries-included Stake Engine game core — RGS transport, boot, round FSM, HUD binding, audio, i18n and a compliant rules builder. Bring a scene, a Present phase and a pure interpretBook; the core does the rest.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"stake",
|