@open-slot-ui/core 0.4.2 → 0.5.0

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/dist/index.d.cts CHANGED
@@ -188,6 +188,13 @@ declare function extendTheme(base: Theme, patch: DeepPartial<Theme>): Theme;
188
188
  type Orientation = 'landscape' | 'portrait';
189
189
  /** A named device bucket. Config can target these to restyle per device. */
190
190
  type Breakpoint = 'mobile' | 'tablet' | 'desktop';
191
+ /** The uniformly-scaled DESIGN FRAME ("stage") rect, in screen pixels. */
192
+ interface StageRect {
193
+ x: number;
194
+ y: number;
195
+ width: number;
196
+ height: number;
197
+ }
191
198
  interface ScreenState {
192
199
  width: number;
193
200
  height: number;
@@ -196,6 +203,15 @@ interface ScreenState {
196
203
  breakpoint: Breakpoint;
197
204
  /** Multiply reference-px sizes by this to fit the current screen. */
198
205
  scale: number;
206
+ /**
207
+ * The reference design frame, scaled UNIFORMLY by `scale` and placed in the viewport
208
+ * (letterboxed by `stageAnchor` when the aspect ratio differs). Every control anchors
209
+ * to THIS rect — not the raw screen — so the whole HUD scales as one homogeneous unit
210
+ * and the relative gaps between controls never change with aspect ratio. At the
211
+ * reference resolution `stage` exactly equals the screen, so authored layouts are
212
+ * pixel-identical there.
213
+ */
214
+ stage: StageRect;
199
215
  }
200
216
  interface LayoutConfig {
201
217
  /** Reference design resolution for landscape / portrait. */
@@ -212,6 +228,14 @@ interface LayoutConfig {
212
228
  mobile: number;
213
229
  tablet: number;
214
230
  };
231
+ /**
232
+ * Where the uniformly-scaled design frame sits in the viewport when the aspect ratio
233
+ * doesn't match the reference (i.e. how the letterbox margins are distributed), as
234
+ * `[x, y]` factors in `0..1`. `[0.5, 1]` (default) = bottom-centre: the bottom bar
235
+ * always hugs the screen bottom and any extra vertical room opens ABOVE (the reel
236
+ * area, filled by the game); horizontal slack splits evenly. `[0.5, 0.5]` centres it.
237
+ */
238
+ stageAnchor: [number, number];
215
239
  }
216
240
  declare const defaultLayoutConfig: LayoutConfig;
217
241
  /** Classify the shorter edge into a named device bucket. Pure, total. */
@@ -236,7 +260,16 @@ interface Placement {
236
260
  /** Rotation in radians (resolved from `LayoutSpec.rotation`, which is degrees). */
237
261
  rotation: number;
238
262
  }
239
- /** Resolve a control's layout against the current screen. Pure math. */
263
+ /**
264
+ * Resolve a control's layout against the current screen. Pure math.
265
+ *
266
+ * Anchors to the uniformly-scaled `screen.stage` frame — NOT the raw screen — so the
267
+ * whole HUD scales as one homogeneous unit: the gap between two controls is always a
268
+ * fixed fraction of the (uniformly-scaled) frame, never a function of the viewport's
269
+ * aspect ratio. That's what stops a growing value or a narrow window from letting
270
+ * controls drift into each other. At the reference resolution `stage` equals the
271
+ * screen, so authored offsets land pixel-identically.
272
+ */
240
273
  declare function resolvePlacement(spec: LayoutSpec, screen: ScreenState): Placement;
241
274
 
242
275
  /**
@@ -800,6 +833,10 @@ declare class ReadoutControl extends Control {
800
833
  readonly value: Signal<number>;
801
834
  /** Whether a `'duration'` readout is currently advancing. */
802
835
  readonly running: Signal<boolean>;
836
+ /** Accent emphasis: the renderer tints the value in the theme accent while on — for
837
+ * a MODIFIED readout (e.g. net position while a bet boost is active). Mirrors
838
+ * {@link ValueDisplay.emphasized}. */
839
+ readonly emphasized: Signal<boolean>;
803
840
  readonly kind: ReadoutKind;
804
841
  readonly label?: string;
805
842
  readonly decimals: number;
@@ -809,6 +846,8 @@ declare class ReadoutControl extends Control {
809
846
  constructor(opts: ReadoutOptions);
810
847
  /** Never-reject: malformed input keeps the last good value (P11). */
811
848
  set(v: number): void;
849
+ /** Accent-tint the value ("this readout is modified"). Strict boolean. */
850
+ setEmphasis(on: boolean): void;
812
851
  get(): number;
813
852
  setCurrency(spec: CurrencySpec): void;
814
853
  /** Advance an elapsed-time readout by `deltaSec` (the renderer calls this each frame). */
@@ -1224,6 +1263,9 @@ declare class OpenUI {
1224
1263
  readonly spin: SpinControl;
1225
1264
  readonly balance: ValueDisplay;
1226
1265
  readonly bet: ValueDisplay;
1266
+ /** Bonus "total win" readout — shown in the buy-feature slot during a bonus/free-
1267
+ * spins round (the renderer swaps it for the buy button). Same money logic as `bet`. */
1268
+ readonly totalWin: ValueDisplay;
1227
1269
  readonly settingsButton: ButtonControl;
1228
1270
  readonly settingsPanel: PanelControl;
1229
1271
  readonly musicSlider: SliderControl;
@@ -1440,11 +1482,13 @@ type BlockSpec = {
1440
1482
  id: string;
1441
1483
  label?: string;
1442
1484
  initial?: number;
1485
+ hint?: string;
1443
1486
  } | {
1444
1487
  kind: 'toggle';
1445
1488
  id: string;
1446
1489
  label?: string;
1447
1490
  on?: boolean;
1491
+ hint?: string;
1448
1492
  } | {
1449
1493
  kind: 'button';
1450
1494
  id: string;
@@ -1452,18 +1496,21 @@ type BlockSpec = {
1452
1496
  action?: 'closePanel' | 'openPanel' | 'emit';
1453
1497
  target?: string;
1454
1498
  role?: string;
1499
+ hint?: string;
1455
1500
  } | {
1456
1501
  kind: 'select';
1457
1502
  id: string;
1458
1503
  label?: string;
1459
1504
  options: SelectOption[];
1460
1505
  index?: number;
1506
+ hint?: string;
1461
1507
  } | {
1462
1508
  kind: 'stepper';
1463
1509
  id: string;
1464
1510
  label?: string;
1465
1511
  levels: number[];
1466
1512
  index?: number;
1513
+ hint?: string;
1467
1514
  } | {
1468
1515
  kind: 'value';
1469
1516
  id: string;
@@ -1712,6 +1759,48 @@ declare function resolveTurboModes(modes: TurboSpec['modes']): string[];
1712
1759
  declare function defineUI(spec: UISpec): UISpec;
1713
1760
  declare function createUI(spec?: UISpec, hooks?: HostHooks): OpenUI;
1714
1761
 
1762
+ /**
1763
+ * Social-mode FORBIDDEN-PHRASE checker (Charter B8/P11). Stake US social/sweepstakes
1764
+ * jurisdiction restricts gambling wording; a game must not surface "bet", "pay(table)",
1765
+ * "funds", "cash", etc. anywhere in its copy. This scans the ENGLISH strings a game
1766
+ * ships (menu blocks, rules, section titles, control labels/hints, the `en` dictionary,
1767
+ * the game name) and reports every restricted phrase with a compliant replacement, so
1768
+ * the classic "we shipped a *paytable* while `pay` is banned" slips through no longer.
1769
+ *
1770
+ * It's a lint, not a gate — never throws; `createUI` runs it automatically in social
1771
+ * mode and forwards findings to `HostHooks.onDataIssue` (+ a console warning), and a
1772
+ * game's own test can call {@link checkSocialPhrases} directly.
1773
+ *
1774
+ * See Stake's "Jurisdiction Requirements → forbidden phrases" for the authoritative
1775
+ * list; this covers the common roots and their recommended replacements.
1776
+ */
1777
+
1778
+ interface ForbiddenMatch {
1779
+ /** The restricted root that matched ("pay*", "bet", "funds", …). */
1780
+ term: string;
1781
+ /** The recommended compliant replacement. */
1782
+ replacement: string;
1783
+ }
1784
+ interface SocialPhraseIssue {
1785
+ /** Where the copy lives (e.g. `menu.rules[3]`, `locale.en["Paytable"]`). */
1786
+ source: string;
1787
+ /** The offending string. */
1788
+ text: string;
1789
+ /** Every restricted phrase found in it. */
1790
+ matches: ForbiddenMatch[];
1791
+ }
1792
+ /** Every restricted phrase in `text` (empty when clean). Pure + total. */
1793
+ declare function findForbiddenPhrases(text: string): ForbiddenMatch[];
1794
+ type Entry = {
1795
+ source: string;
1796
+ text: string;
1797
+ };
1798
+ /** Collect every English string a spec ships (menu/rules blocks, titles, labels+hints,
1799
+ * game name, the `en` dictionary) with a source path — the input to {@link checkSocialPhrases}. */
1800
+ declare function collectSocialCopy(spec: UISpec): Entry[];
1801
+ /** Scan a whole spec's English copy and return every restricted-phrase finding. */
1802
+ declare function checkSocialPhrases(spec: UISpec): SocialPhraseIssue[];
1803
+
1715
1804
  /**
1716
1805
  * The responsive layer: re-apply per-device / per-orientation control overrides
1717
1806
  * every time the screen bucket changes. Pure assembly over the existing layout
@@ -1772,36 +1861,49 @@ declare class EventLog {
1772
1861
  /**
1773
1862
  * Currency display metadata + a pure formatter (Charter P11: data, not code).
1774
1863
  *
1775
- * Stake Engine supports 35+ currencies fiat (incl. zero-decimal JPY/KRW/VND…),
1776
- * crypto, and the social coins XGC/XSC (shown as "GC"/"SC", never locale-formatted
1777
- * as fiat). A host shouldn't have to hand-tune decimals for each, so this maps a
1778
- * code its display precision; anything unknown defaults to 2 (the common case).
1779
- * The UI computes no money it only formats what the game sets.
1864
+ * This is the **Stake Engine currency table**: every code Stake supports, mapped to
1865
+ * its display SYMBOL, minor-unit decimals, and symbol side (before/after the number).
1866
+ * A game shouldn't hand-tune any of this it either passes a full `CurrencySpec`, or
1867
+ * just the CODE (`currency: 'CNY'`) and gets the Stake formatting for free
1868
+ * ("I use the Stake currency formatter"). Unknown codes fall back to a 2-decimal,
1869
+ * code-suffix spec so nothing ever breaks. The UI computes no money — it only formats
1870
+ * what the game sets.
1780
1871
  */
1781
1872
 
1782
1873
  interface CurrencyInfo {
1783
- /** Fractional digits to show (JPY/KRW = 0, USD = 2, BTC = 8). */
1874
+ /** The glyph shown to the player ("$", "CN¥", "GC"). */
1875
+ symbol: string;
1876
+ /** Fractional digits to show (JPY/KRW/IDR = 0, USD = 2, BTC = 8). */
1784
1877
  decimals: number;
1785
- /** Display code shown to the player when it differs from the code (XGC → "GC"). */
1786
- display?: string;
1787
- /** A social / sweepstakes coin never formatted as a fiat currency. */
1878
+ /** Symbol AFTER the number ("10,00 zł") rather than before ("$10.00"). Default before. */
1879
+ symbolAfter?: boolean;
1880
+ /** A social / sweepstakes coin (Gold Coins / Stake Cash) — never fiat wording. */
1788
1881
  social?: boolean;
1789
1882
  }
1790
- /** Code → display info. Unknown codes fall back to 2 decimals (see {@link resolveCurrency}). */
1883
+ /**
1884
+ * The Stake Engine supported-currency matrix (symbol · decimals · placement), 1:1 with
1885
+ * the platform's `CurrencyMeta`. Symbol placement: prefix by default, SUFFIX for the
1886
+ * few that render after the amount (DKK/PLN/VND/CLP/ARS/PEN). Crypto is included for
1887
+ * completeness (8-dp with a symbol) so high-precision balances also format right.
1888
+ */
1791
1889
  declare const CURRENCY_TABLE: Readonly<Record<string, CurrencyInfo>>;
1792
- /** True if `code` is a Stake social/sweepstakes coin (display "GC"/"SC"). */
1890
+ /** True if `code` is a Stake social/sweepstakes coin (displayed "GC"/"SC"). */
1793
1891
  declare function isSocialCurrency(code: string): boolean;
1794
1892
  /**
1795
- * Resolve a currency code to a complete, display-ready {@link CurrencySpec}: fills
1796
- * decimals from the table (default 2) and maps social coins to their display code.
1797
- * Anything in `overrides` wins, so a game can still hand-tune. Never throws.
1893
+ * Resolve a currency code to a complete, display-ready {@link CurrencySpec} using the
1894
+ * Stake table: fills the SYMBOL (rendered tight, `display:'symbol'`), its side
1895
+ * (`position`), and the minor-unit `decimals`. Anything in `overrides` wins so a game
1896
+ * can still hand-tune (e.g. force more decimals for sub-cent payouts). An unknown code
1897
+ * degrades to a 2-decimal code-suffix spec. Never throws.
1798
1898
  */
1799
1899
  declare function resolveCurrency(code: string, overrides?: Partial<CurrencySpec>): CurrencySpec;
1800
1900
  /**
1801
1901
  * Format an amount (major units) per a {@link CurrencySpec}: grouped integer part,
1802
- * fixed decimals, the code on the configured side. `signed` adds an explicit "+"
1803
- * for non-negative values (negatives always show "-") used by the net-position
1804
- * readout. Pure + total: a malformed number renders as "0".
1902
+ * fixed decimals, and the affix on the configured side. When `display:'symbol'` (and a
1903
+ * symbol is set) the SYMBOL sits tight against the number (`CN¥1,234.56` · `1.234,56 zł`);
1904
+ * otherwise the ISO code is spaced (`1,234.56 USD`). `signed` adds an explicit "+" for
1905
+ * non-negative values (used by the net-position readout). Pure + total: a malformed
1906
+ * number renders as "0".
1805
1907
  */
1806
1908
  declare function formatAmount(value: number, spec: CurrencySpec, opts?: {
1807
1909
  signed?: boolean;
@@ -1834,4 +1936,4 @@ declare function buildBetLadder(cfg: RgsBetConfig, divisor?: number): {
1834
1936
  /** Clamp a stake (major units) to the RGS min/max and snap to `stepBet`. Pure. */
1835
1937
  declare function clampBet(amount: number, cfg: RgsBetConfig, divisor?: number): number;
1836
1938
 
1837
- export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
1939
+ export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type ForbiddenMatch, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SocialPhraseIssue, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StageRect, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, checkSocialPhrases, clampBet, clampDecimals, clampDigits, clampMinDigits, collectSocialCopy, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, findForbiddenPhrases, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
package/dist/index.d.ts CHANGED
@@ -188,6 +188,13 @@ declare function extendTheme(base: Theme, patch: DeepPartial<Theme>): Theme;
188
188
  type Orientation = 'landscape' | 'portrait';
189
189
  /** A named device bucket. Config can target these to restyle per device. */
190
190
  type Breakpoint = 'mobile' | 'tablet' | 'desktop';
191
+ /** The uniformly-scaled DESIGN FRAME ("stage") rect, in screen pixels. */
192
+ interface StageRect {
193
+ x: number;
194
+ y: number;
195
+ width: number;
196
+ height: number;
197
+ }
191
198
  interface ScreenState {
192
199
  width: number;
193
200
  height: number;
@@ -196,6 +203,15 @@ interface ScreenState {
196
203
  breakpoint: Breakpoint;
197
204
  /** Multiply reference-px sizes by this to fit the current screen. */
198
205
  scale: number;
206
+ /**
207
+ * The reference design frame, scaled UNIFORMLY by `scale` and placed in the viewport
208
+ * (letterboxed by `stageAnchor` when the aspect ratio differs). Every control anchors
209
+ * to THIS rect — not the raw screen — so the whole HUD scales as one homogeneous unit
210
+ * and the relative gaps between controls never change with aspect ratio. At the
211
+ * reference resolution `stage` exactly equals the screen, so authored layouts are
212
+ * pixel-identical there.
213
+ */
214
+ stage: StageRect;
199
215
  }
200
216
  interface LayoutConfig {
201
217
  /** Reference design resolution for landscape / portrait. */
@@ -212,6 +228,14 @@ interface LayoutConfig {
212
228
  mobile: number;
213
229
  tablet: number;
214
230
  };
231
+ /**
232
+ * Where the uniformly-scaled design frame sits in the viewport when the aspect ratio
233
+ * doesn't match the reference (i.e. how the letterbox margins are distributed), as
234
+ * `[x, y]` factors in `0..1`. `[0.5, 1]` (default) = bottom-centre: the bottom bar
235
+ * always hugs the screen bottom and any extra vertical room opens ABOVE (the reel
236
+ * area, filled by the game); horizontal slack splits evenly. `[0.5, 0.5]` centres it.
237
+ */
238
+ stageAnchor: [number, number];
215
239
  }
216
240
  declare const defaultLayoutConfig: LayoutConfig;
217
241
  /** Classify the shorter edge into a named device bucket. Pure, total. */
@@ -236,7 +260,16 @@ interface Placement {
236
260
  /** Rotation in radians (resolved from `LayoutSpec.rotation`, which is degrees). */
237
261
  rotation: number;
238
262
  }
239
- /** Resolve a control's layout against the current screen. Pure math. */
263
+ /**
264
+ * Resolve a control's layout against the current screen. Pure math.
265
+ *
266
+ * Anchors to the uniformly-scaled `screen.stage` frame — NOT the raw screen — so the
267
+ * whole HUD scales as one homogeneous unit: the gap between two controls is always a
268
+ * fixed fraction of the (uniformly-scaled) frame, never a function of the viewport's
269
+ * aspect ratio. That's what stops a growing value or a narrow window from letting
270
+ * controls drift into each other. At the reference resolution `stage` equals the
271
+ * screen, so authored offsets land pixel-identically.
272
+ */
240
273
  declare function resolvePlacement(spec: LayoutSpec, screen: ScreenState): Placement;
241
274
 
242
275
  /**
@@ -800,6 +833,10 @@ declare class ReadoutControl extends Control {
800
833
  readonly value: Signal<number>;
801
834
  /** Whether a `'duration'` readout is currently advancing. */
802
835
  readonly running: Signal<boolean>;
836
+ /** Accent emphasis: the renderer tints the value in the theme accent while on — for
837
+ * a MODIFIED readout (e.g. net position while a bet boost is active). Mirrors
838
+ * {@link ValueDisplay.emphasized}. */
839
+ readonly emphasized: Signal<boolean>;
803
840
  readonly kind: ReadoutKind;
804
841
  readonly label?: string;
805
842
  readonly decimals: number;
@@ -809,6 +846,8 @@ declare class ReadoutControl extends Control {
809
846
  constructor(opts: ReadoutOptions);
810
847
  /** Never-reject: malformed input keeps the last good value (P11). */
811
848
  set(v: number): void;
849
+ /** Accent-tint the value ("this readout is modified"). Strict boolean. */
850
+ setEmphasis(on: boolean): void;
812
851
  get(): number;
813
852
  setCurrency(spec: CurrencySpec): void;
814
853
  /** Advance an elapsed-time readout by `deltaSec` (the renderer calls this each frame). */
@@ -1224,6 +1263,9 @@ declare class OpenUI {
1224
1263
  readonly spin: SpinControl;
1225
1264
  readonly balance: ValueDisplay;
1226
1265
  readonly bet: ValueDisplay;
1266
+ /** Bonus "total win" readout — shown in the buy-feature slot during a bonus/free-
1267
+ * spins round (the renderer swaps it for the buy button). Same money logic as `bet`. */
1268
+ readonly totalWin: ValueDisplay;
1227
1269
  readonly settingsButton: ButtonControl;
1228
1270
  readonly settingsPanel: PanelControl;
1229
1271
  readonly musicSlider: SliderControl;
@@ -1440,11 +1482,13 @@ type BlockSpec = {
1440
1482
  id: string;
1441
1483
  label?: string;
1442
1484
  initial?: number;
1485
+ hint?: string;
1443
1486
  } | {
1444
1487
  kind: 'toggle';
1445
1488
  id: string;
1446
1489
  label?: string;
1447
1490
  on?: boolean;
1491
+ hint?: string;
1448
1492
  } | {
1449
1493
  kind: 'button';
1450
1494
  id: string;
@@ -1452,18 +1496,21 @@ type BlockSpec = {
1452
1496
  action?: 'closePanel' | 'openPanel' | 'emit';
1453
1497
  target?: string;
1454
1498
  role?: string;
1499
+ hint?: string;
1455
1500
  } | {
1456
1501
  kind: 'select';
1457
1502
  id: string;
1458
1503
  label?: string;
1459
1504
  options: SelectOption[];
1460
1505
  index?: number;
1506
+ hint?: string;
1461
1507
  } | {
1462
1508
  kind: 'stepper';
1463
1509
  id: string;
1464
1510
  label?: string;
1465
1511
  levels: number[];
1466
1512
  index?: number;
1513
+ hint?: string;
1467
1514
  } | {
1468
1515
  kind: 'value';
1469
1516
  id: string;
@@ -1712,6 +1759,48 @@ declare function resolveTurboModes(modes: TurboSpec['modes']): string[];
1712
1759
  declare function defineUI(spec: UISpec): UISpec;
1713
1760
  declare function createUI(spec?: UISpec, hooks?: HostHooks): OpenUI;
1714
1761
 
1762
+ /**
1763
+ * Social-mode FORBIDDEN-PHRASE checker (Charter B8/P11). Stake US social/sweepstakes
1764
+ * jurisdiction restricts gambling wording; a game must not surface "bet", "pay(table)",
1765
+ * "funds", "cash", etc. anywhere in its copy. This scans the ENGLISH strings a game
1766
+ * ships (menu blocks, rules, section titles, control labels/hints, the `en` dictionary,
1767
+ * the game name) and reports every restricted phrase with a compliant replacement, so
1768
+ * the classic "we shipped a *paytable* while `pay` is banned" slips through no longer.
1769
+ *
1770
+ * It's a lint, not a gate — never throws; `createUI` runs it automatically in social
1771
+ * mode and forwards findings to `HostHooks.onDataIssue` (+ a console warning), and a
1772
+ * game's own test can call {@link checkSocialPhrases} directly.
1773
+ *
1774
+ * See Stake's "Jurisdiction Requirements → forbidden phrases" for the authoritative
1775
+ * list; this covers the common roots and their recommended replacements.
1776
+ */
1777
+
1778
+ interface ForbiddenMatch {
1779
+ /** The restricted root that matched ("pay*", "bet", "funds", …). */
1780
+ term: string;
1781
+ /** The recommended compliant replacement. */
1782
+ replacement: string;
1783
+ }
1784
+ interface SocialPhraseIssue {
1785
+ /** Where the copy lives (e.g. `menu.rules[3]`, `locale.en["Paytable"]`). */
1786
+ source: string;
1787
+ /** The offending string. */
1788
+ text: string;
1789
+ /** Every restricted phrase found in it. */
1790
+ matches: ForbiddenMatch[];
1791
+ }
1792
+ /** Every restricted phrase in `text` (empty when clean). Pure + total. */
1793
+ declare function findForbiddenPhrases(text: string): ForbiddenMatch[];
1794
+ type Entry = {
1795
+ source: string;
1796
+ text: string;
1797
+ };
1798
+ /** Collect every English string a spec ships (menu/rules blocks, titles, labels+hints,
1799
+ * game name, the `en` dictionary) with a source path — the input to {@link checkSocialPhrases}. */
1800
+ declare function collectSocialCopy(spec: UISpec): Entry[];
1801
+ /** Scan a whole spec's English copy and return every restricted-phrase finding. */
1802
+ declare function checkSocialPhrases(spec: UISpec): SocialPhraseIssue[];
1803
+
1715
1804
  /**
1716
1805
  * The responsive layer: re-apply per-device / per-orientation control overrides
1717
1806
  * every time the screen bucket changes. Pure assembly over the existing layout
@@ -1772,36 +1861,49 @@ declare class EventLog {
1772
1861
  /**
1773
1862
  * Currency display metadata + a pure formatter (Charter P11: data, not code).
1774
1863
  *
1775
- * Stake Engine supports 35+ currencies fiat (incl. zero-decimal JPY/KRW/VND…),
1776
- * crypto, and the social coins XGC/XSC (shown as "GC"/"SC", never locale-formatted
1777
- * as fiat). A host shouldn't have to hand-tune decimals for each, so this maps a
1778
- * code its display precision; anything unknown defaults to 2 (the common case).
1779
- * The UI computes no money it only formats what the game sets.
1864
+ * This is the **Stake Engine currency table**: every code Stake supports, mapped to
1865
+ * its display SYMBOL, minor-unit decimals, and symbol side (before/after the number).
1866
+ * A game shouldn't hand-tune any of this it either passes a full `CurrencySpec`, or
1867
+ * just the CODE (`currency: 'CNY'`) and gets the Stake formatting for free
1868
+ * ("I use the Stake currency formatter"). Unknown codes fall back to a 2-decimal,
1869
+ * code-suffix spec so nothing ever breaks. The UI computes no money — it only formats
1870
+ * what the game sets.
1780
1871
  */
1781
1872
 
1782
1873
  interface CurrencyInfo {
1783
- /** Fractional digits to show (JPY/KRW = 0, USD = 2, BTC = 8). */
1874
+ /** The glyph shown to the player ("$", "CN¥", "GC"). */
1875
+ symbol: string;
1876
+ /** Fractional digits to show (JPY/KRW/IDR = 0, USD = 2, BTC = 8). */
1784
1877
  decimals: number;
1785
- /** Display code shown to the player when it differs from the code (XGC → "GC"). */
1786
- display?: string;
1787
- /** A social / sweepstakes coin never formatted as a fiat currency. */
1878
+ /** Symbol AFTER the number ("10,00 zł") rather than before ("$10.00"). Default before. */
1879
+ symbolAfter?: boolean;
1880
+ /** A social / sweepstakes coin (Gold Coins / Stake Cash) — never fiat wording. */
1788
1881
  social?: boolean;
1789
1882
  }
1790
- /** Code → display info. Unknown codes fall back to 2 decimals (see {@link resolveCurrency}). */
1883
+ /**
1884
+ * The Stake Engine supported-currency matrix (symbol · decimals · placement), 1:1 with
1885
+ * the platform's `CurrencyMeta`. Symbol placement: prefix by default, SUFFIX for the
1886
+ * few that render after the amount (DKK/PLN/VND/CLP/ARS/PEN). Crypto is included for
1887
+ * completeness (8-dp with a symbol) so high-precision balances also format right.
1888
+ */
1791
1889
  declare const CURRENCY_TABLE: Readonly<Record<string, CurrencyInfo>>;
1792
- /** True if `code` is a Stake social/sweepstakes coin (display "GC"/"SC"). */
1890
+ /** True if `code` is a Stake social/sweepstakes coin (displayed "GC"/"SC"). */
1793
1891
  declare function isSocialCurrency(code: string): boolean;
1794
1892
  /**
1795
- * Resolve a currency code to a complete, display-ready {@link CurrencySpec}: fills
1796
- * decimals from the table (default 2) and maps social coins to their display code.
1797
- * Anything in `overrides` wins, so a game can still hand-tune. Never throws.
1893
+ * Resolve a currency code to a complete, display-ready {@link CurrencySpec} using the
1894
+ * Stake table: fills the SYMBOL (rendered tight, `display:'symbol'`), its side
1895
+ * (`position`), and the minor-unit `decimals`. Anything in `overrides` wins so a game
1896
+ * can still hand-tune (e.g. force more decimals for sub-cent payouts). An unknown code
1897
+ * degrades to a 2-decimal code-suffix spec. Never throws.
1798
1898
  */
1799
1899
  declare function resolveCurrency(code: string, overrides?: Partial<CurrencySpec>): CurrencySpec;
1800
1900
  /**
1801
1901
  * Format an amount (major units) per a {@link CurrencySpec}: grouped integer part,
1802
- * fixed decimals, the code on the configured side. `signed` adds an explicit "+"
1803
- * for non-negative values (negatives always show "-") used by the net-position
1804
- * readout. Pure + total: a malformed number renders as "0".
1902
+ * fixed decimals, and the affix on the configured side. When `display:'symbol'` (and a
1903
+ * symbol is set) the SYMBOL sits tight against the number (`CN¥1,234.56` · `1.234,56 zł`);
1904
+ * otherwise the ISO code is spaced (`1,234.56 USD`). `signed` adds an explicit "+" for
1905
+ * non-negative values (used by the net-position readout). Pure + total: a malformed
1906
+ * number renders as "0".
1805
1907
  */
1806
1908
  declare function formatAmount(value: number, spec: CurrencySpec, opts?: {
1807
1909
  signed?: boolean;
@@ -1834,4 +1936,4 @@ declare function buildBetLadder(cfg: RgsBetConfig, divisor?: number): {
1834
1936
  /** Clamp a stake (major units) to the RGS min/max and snap to `stepBet`. Pure. */
1835
1937
  declare function clampBet(amount: number, cfg: RgsBetConfig, divisor?: number): number;
1836
1938
 
1837
- export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, clampBet, clampDecimals, clampDigits, clampMinDigits, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };
1939
+ export { API_AMOUNT_DIVISOR, type Anchor, AutoplayControl, type AutoplayLimits, type AutoplayMode, type AutoplayOptions, BLOCK_KINDS, type BlockFactory, type BlockSpec, type Breakpoint, type BuiltPanel, ButtonControl, type ButtonOptions, CURRENCY_TABLE, CardControl, type CardOptions, type ComposeMenuOptions, Control, type ControlOptions, type ControlOverride, ControlRegistry, type ControlSnapshot, type CurrencyInfo, type CurrencySpec, DEFAULT_MENU_TITLES, DEFAULT_NOTICE_ACTION, type DeepPartial, DictionaryTranslator, type Dispose, EventBus, EventLog, type EventLogOptions, type EventMap, Fade, type ForbiddenMatch, type HostHooks, type JurisdictionConfig, type KnownControlId, LOCALE_LABELS, type LayoutConfig, type LayoutSpec, type LoggedEvent, type MenuSpec, type NoticeAction, type NoticeOptions, OpenUI, type OpenUIEvents, type OpenUIOptions, type Orientation, PanelControl, type PanelOptions, type PanelSpec, type PanelVariant, Parallel, type Placement, Pulse, RGS_ERROR_KEYS, ReadoutControl, type ReadoutKind, type ReadoutOptions, type RealityCheckOptions, type Rect, type ResponsiveKey, type ResponsiveOverride, type RgsBetConfig, type RgsErrorCode, type RgsErrorOptions, type ScreenState, SelectControl, type SelectControlOptions, type SelectOption, Sequence, Signal, SliderControl, type SliderOptions, type SocialPhraseIssue, type SpecIssue, SpinControl, type SpinPress, type SpinSpec, Squish, type StageRect, type StateDef, type StateMap, StepperControl, type StepperOptions, type Subscribable, type Theme, type ThemeChoice, type ThemeIssue, type ThemeOverrides, type ThemePreset, ToggleControl, type ToggleOptions, type Transition, type Translator, TurboControl, type TurboOptions, type TurboSpec, Turn, type UISpec, ValueDisplay, type ValueDisplayOptions, type ViewInspect, applyJurisdiction, breakpointFor, buildBetLadder, buildBlocks, buildPanel, buttonBlocks, checkSocialPhrases, clampBet, clampDecimals, clampDigits, clampMinDigits, collectSocialCopy, composeMenu, computeScreen, createUI, defaultHudSpec, defaultLayout, defaultLayoutConfig, defaultTheme, defineUI, dictionary, displayDigits, effect, errorBlocks, extendTheme, findForbiddenPhrases, formatAmount, installResponsive, integerDigits, isSafeColor, isSocialCurrency, landscapeDefaultLayouts, openuiDefaults, openuiSocialDefaults, portraitDefaultLayouts, resolveCurrency, resolvePlacement, resolveTheme, resolveTurboModes, safeAmount, sanitizeThemeOverrides, themePresets, validateSpec, valueFitMaxWidth };