@energy8platform/shell 0.5.0 → 0.6.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/dist/pixi.d.ts CHANGED
@@ -23,6 +23,9 @@ declare class EventEmitter<TEvents extends {}> {
23
23
  * `bonus` pairs with `setBonus()` to show a game-supplied label + value (adventure, hold-and-spin,
24
24
  * respins — anything that isn't a plain free-spins counter). */
25
25
  type ShellMode = 'base' | 'bonus' | 'freeSpins' | 'replay';
26
+ /** The three independent volume sliders shown in the Settings overlay. */
27
+ type VolumeKey = 'master' | 'music' | 'sfx';
28
+ type VolumeLevels = Record<VolumeKey, number>;
26
29
  interface CurrencyConfig {
27
30
  symbol: string;
28
31
  position: 'left' | 'right';
@@ -284,9 +287,13 @@ interface ShellConfig {
284
287
  * opening the built-in buy-bonus overlay (e.g. the game shows its own bonus UI). The button
285
288
  * is shown whenever this OR `features.buyBonus` is set. */
286
289
  onBonusBuy?: () => void;
290
+ /** Initial Settings-overlay volume slider positions (each 0..1, defaults to 1 = 100%). The shell
291
+ * keeps them stateful across opens; read/update at runtime via `shell.getVolume()` /
292
+ * `shell.setVolume()`, and listen to `settingChange` ({ key: 'master'|'music'|'sfx' }) to apply. */
293
+ volumes?: Partial<VolumeLevels>;
287
294
  }
288
295
  /** ShellConfig after the controller applies defaults (version, isSocial, replay, theme). No mount. */
289
- type ResolvedShellConfig = Required<Pick<ShellConfig, 'language' | 'currency' | 'availableBets' | 'defaultBet' | 'balance' | 'win' | 'mode' | 'features' | 'gameInfo' | 'version' | 'isSocial' | 'replay'>> & Pick<ShellConfig, 'currentBet' | 'theme' | 'onBonusBuy'>;
296
+ type ResolvedShellConfig = Required<Pick<ShellConfig, 'language' | 'currency' | 'availableBets' | 'defaultBet' | 'balance' | 'win' | 'mode' | 'features' | 'gameInfo' | 'version' | 'isSocial' | 'replay'>> & Pick<ShellConfig, 'currentBet' | 'theme' | 'onBonusBuy' | 'volumes'>;
290
297
  interface ShellState {
291
298
  mode: ShellMode;
292
299
  /** Sticky replay marker — true for a historical-round replay, regardless of the current
@@ -311,6 +318,9 @@ interface ShellState {
311
318
  /** The currently activated `feature` option (e.g. Ante), or null. Drives the
312
319
  * effective-bet readout tint and the BUY BONUS → DISABLE toggle on the bar. */
313
320
  activeFeature: BonusOption$1 | null;
321
+ /** Volume slider positions (0..1) surfaced in the Settings overlay. Stateful across opens so a
322
+ * reopened overlay reflects the last-set positions instead of resetting to 100%. */
323
+ volumes: VolumeLevels;
314
324
  }
315
325
  interface ShellEvents {
316
326
  spin: void;
@@ -433,6 +443,13 @@ interface ShellHost {
433
443
  setSound(on: boolean): void;
434
444
  /** An open Settings overlay registers an icon updater here (null clears it on close). */
435
445
  setSoundRefresh(fn: ((on: boolean) => void) | null): void;
446
+ /** Current volume slider position (0..1) for master/music/sfx. */
447
+ getVolume(key: VolumeKey): number;
448
+ /** Set a volume slider (0..1): clamps, stores, emits `settingChange`, and live-updates an open
449
+ * Settings overlay. Called by the slider control on drag AND by game code as the public API. */
450
+ setVolume(key: VolumeKey, value: number): void;
451
+ /** An open Settings overlay registers a slider updater here (null clears it on close). */
452
+ setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void;
436
453
  /** Logic-bearing actions invoked by renderer controls. */
437
454
  readonly actions: ShellActions;
438
455
  }
@@ -496,13 +513,14 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
496
513
  tokens: ShellTokens;
497
514
  layout: ShellLayoutMode;
498
515
  soundOn: boolean;
499
- readonly engineVersion = "0.5.0";
516
+ readonly engineVersion = "0.6.1";
500
517
  readonly actions: ShellActions;
501
518
  private renderer;
502
519
  private i18n;
503
520
  private kbd?;
504
521
  private overlay;
505
522
  private soundRefresh;
523
+ private volumeRefresh;
506
524
  private prevBalance;
507
525
  private prevWin;
508
526
  private destroyed;
@@ -527,11 +545,22 @@ declare class ShellController extends EventEmitter<ShellEvents> implements Shell
527
545
  closeModal(): void;
528
546
  setSound(on: boolean): void;
529
547
  setSoundRefresh(fn: ((on: boolean) => void) | null): void;
548
+ getVolume(key: VolumeKey): number;
549
+ /** Set a volume slider (0..1). Shared by the slider control (drag) and game code (public API):
550
+ * clamps, stores so a reopened Settings overlay reflects it, emits `settingChange`, and
551
+ * live-updates the slider if the overlay is currently open. */
552
+ setVolume(key: VolumeKey, value: number): void;
553
+ setVolumeRefresh(fn: ((key: VolumeKey, value: number) => void) | null): void;
530
554
  activateFeature(bonus: BonusOption$1): void;
531
555
  deactivateFeature(): void;
532
556
  private money;
533
557
  setBalance(n: number): void;
534
- setWin(n: number): void;
558
+ /** Set the WIN readout. Counts up/down from the previous value by default. Pass
559
+ * `{ animate: false }` to SNAP instantly (renderBar cancels any in-flight count-up) — used by the
560
+ * host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong. */
561
+ setWin(n: number, opts?: {
562
+ animate?: boolean;
563
+ }): void;
535
564
  setBet(n: number): void;
536
565
  setMode(mode: ShellMode): void;
537
566
  setBusy(busy: boolean): void;
@@ -566,7 +595,7 @@ interface I18n {
566
595
  declare function createI18n(opts: I18nOptions): I18n;
567
596
 
568
597
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
569
- declare const PACKAGE_VERSION = "0.5.0";
598
+ declare const PACKAGE_VERSION = "0.6.1";
570
599
 
571
600
  /** A shell: the renderer-agnostic controller plus the surface facade (safeArea/barHeight/setVisible)
572
601
  * an embedding host reads. `createShell`, `createGameShell` and `createPixiShell` all return this. */
@@ -706,4 +735,4 @@ declare function createPixiShell(config: PixiShellConfig): PixiGameShell;
706
735
  declare function removePixiShell(): Promise<void>;
707
736
 
708
737
  export { DEFAULT_ACCENT, PACKAGE_VERSION, PixiRenderer, SCHEMES, ShellController, createI18n, createPixiShell, createShell, normalizeLang, removePixiShell, resolveConfig, resolveTheme, socialize };
709
- export type { AutoplayConfig, AutoplayOptions, BonusCardContext, BonusOption, BonusReadout, CellRef, CreateShellOptions, CurrencyConfig, FreeSpinsState, GameInfoContent, GameInfoSection, GameMode, I18n, I18nOptions, Lang, ModalAction, ModalOptions, OverlayHandle, OverlayRequest, PaylineDef, PaytableRow, PixiGameShell, PixiShellConfig, PixiShellSurface, ReplayModalOptions, ResolvedShellConfig, SafeArea, ShapeDef, Shell, ShellActions, ShellConfig, ShellEvents, ShellFeatures, ShellHost, ShellLayoutMode, ShellMode, ShellRenderer, ShellState, ShellSurface, ShellTokens, ThemeConfig, WinSection };
738
+ export type { AutoplayConfig, AutoplayOptions, BonusCardContext, BonusOption, BonusReadout, CellRef, CreateShellOptions, CurrencyConfig, FreeSpinsState, GameInfoContent, GameInfoSection, GameMode, I18n, I18nOptions, Lang, ModalAction, ModalOptions, OverlayHandle, OverlayRequest, PaylineDef, PaytableRow, PixiGameShell, PixiShellConfig, PixiShellSurface, ReplayModalOptions, ResolvedShellConfig, SafeArea, ShapeDef, Shell, ShellActions, ShellConfig, ShellEvents, ShellFeatures, ShellHost, ShellLayoutMode, ShellMode, ShellRenderer, ShellState, ShellSurface, ShellTokens, ThemeConfig, VolumeKey, VolumeLevels, WinSection };
package/dist/pixi.esm.js CHANGED
@@ -66,8 +66,17 @@ function createInitialState(config) {
66
66
  freeSpins: { current: 0, total: 0, totalWin: 0 },
67
67
  bonus: null,
68
68
  activeFeature: null,
69
+ volumes: {
70
+ master: clampVolume(config.volumes?.master),
71
+ music: clampVolume(config.volumes?.music),
72
+ sfx: clampVolume(config.volumes?.sfx),
73
+ },
69
74
  };
70
75
  }
76
+ /** Clamp a configured volume to 0..1, defaulting to full (1) when unset/invalid. */
77
+ function clampVolume(v) {
78
+ return typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
79
+ }
71
80
  /** Step bet up/down within availableBets, clamped at the ends. */
72
81
  function stepBet(state, direction) {
73
82
  const idx = state.availableBets.indexOf(state.bet);
@@ -1438,7 +1447,7 @@ class KeyboardController {
1438
1447
 
1439
1448
  // AUTO-GENERATED by scripts/gen-version.mjs — do not edit. Mirrors package.json "version".
1440
1449
  /** The @energy8platform/shell package version, stamped into the game-info footer. */
1441
- const PACKAGE_VERSION = '0.5.0';
1450
+ const PACKAGE_VERSION = '0.6.1';
1442
1451
 
1443
1452
  /** Apply defaults to the raw config (the mount target lives on the renderer, not here). */
1444
1453
  function resolveConfig(config) {
@@ -1455,6 +1464,7 @@ function resolveConfig(config) {
1455
1464
  features: config.features,
1456
1465
  theme: config.theme,
1457
1466
  onBonusBuy: config.onBonusBuy,
1467
+ volumes: config.volumes,
1458
1468
  version: config.version ?? '1.0.0',
1459
1469
  isSocial: config.isSocial ?? false,
1460
1470
  replay: config.replay ?? config.mode === 'replay',
@@ -1476,6 +1486,7 @@ class ShellController extends EventEmitter {
1476
1486
  kbd;
1477
1487
  overlay = null;
1478
1488
  soundRefresh = null;
1489
+ volumeRefresh = null;
1479
1490
  prevBalance;
1480
1491
  prevWin;
1481
1492
  destroyed = false;
@@ -1659,6 +1670,7 @@ class ShellController extends EventEmitter {
1659
1670
  return;
1660
1671
  this.overlay = null;
1661
1672
  this.soundRefresh = null;
1673
+ this.volumeRefresh = null;
1662
1674
  this.renderer.closeOverlay();
1663
1675
  }
1664
1676
  // ── sound ──────────────────────────────────────────────────────────────────
@@ -1671,6 +1683,22 @@ class ShellController extends EventEmitter {
1671
1683
  setSoundRefresh(fn) {
1672
1684
  this.soundRefresh = fn;
1673
1685
  }
1686
+ // ── volume ─────────────────────────────────────────────────────────────────
1687
+ getVolume(key) {
1688
+ return this.state.volumes[key];
1689
+ }
1690
+ /** Set a volume slider (0..1). Shared by the slider control (drag) and game code (public API):
1691
+ * clamps, stores so a reopened Settings overlay reflects it, emits `settingChange`, and
1692
+ * live-updates the slider if the overlay is currently open. */
1693
+ setVolume(key, value) {
1694
+ const v = Math.max(0, Math.min(1, value));
1695
+ this.state.volumes[key] = v;
1696
+ this.emit('settingChange', { key, value: v });
1697
+ this.volumeRefresh?.(key, v);
1698
+ }
1699
+ setVolumeRefresh(fn) {
1700
+ this.volumeRefresh = fn;
1701
+ }
1674
1702
  // ── features ─────────────────────────────────────────────────────────────────
1675
1703
  activateFeature(bonus) {
1676
1704
  this.state.activeFeature = bonus;
@@ -1697,10 +1725,17 @@ class ShellController extends EventEmitter {
1697
1725
  this.prevBalance = n;
1698
1726
  this.money('balance', from, n);
1699
1727
  }
1700
- setWin(n) {
1728
+ /** Set the WIN readout. Counts up/down from the previous value by default. Pass
1729
+ * `{ animate: false }` to SNAP instantly (renderBar cancels any in-flight count-up) — used by the
1730
+ * host to clear WIN to 0 at spin start, where an animated count-DOWN would look wrong. */
1731
+ setWin(n, opts) {
1701
1732
  const from = this.prevWin;
1702
1733
  this.state.win = n;
1703
1734
  this.prevWin = n;
1735
+ if (opts?.animate === false) {
1736
+ this.renderer.renderBar(); // instant repaint from state; cancels running money anims
1737
+ return;
1738
+ }
1704
1739
  this.money('win', from, n);
1705
1740
  }
1706
1741
  setBet(n) {
@@ -3947,6 +3982,12 @@ class Slider extends Container {
3947
3982
  this.thumb.circle(cx, cy, r).fill('#ffffff');
3948
3983
  this.hitArea = new Rectangle(0, 0, this.w, h);
3949
3984
  }
3985
+ /** Programmatically move the thumb (0..1) without firing onInput — for live updates driven by
3986
+ * `host.setVolume()` while the overlay is open. */
3987
+ setValue(v) {
3988
+ this._value = Math.max(0, Math.min(1, v));
3989
+ this.draw();
3990
+ }
3950
3991
  setLayoutSize(w) {
3951
3992
  if (w != null)
3952
3993
  this.w = w;
@@ -4035,10 +4076,13 @@ function buildBody$1(host, width) {
4035
4076
  speaker.active = on;
4036
4077
  });
4037
4078
  col.add(glassRow(host, [textNode(host, host.t('Sound')), new Spacer(), speaker]));
4038
- // Volume sliders
4039
- col.add(sliderRow(host, width, 'master', host.t('Master volume')));
4040
- col.add(sliderRow(host, width, 'music', host.t('Music')));
4041
- col.add(sliderRow(host, width, 'sfx', host.t('SFX')));
4079
+ // Volume sliders — positions read from the shell's stored volumes (stateful across opens); the
4080
+ // registered refreshers let `host.setVolume()` from game code move the thumbs live.
4081
+ const updaters = {};
4082
+ col.add(sliderRow(host, width, 'master', host.t('Master volume'), updaters));
4083
+ col.add(sliderRow(host, width, 'music', host.t('Music'), updaters));
4084
+ col.add(sliderRow(host, width, 'sfx', host.t('SFX'), updaters));
4085
+ host.setVolumeRefresh?.((key, v) => updaters[key]?.(v));
4042
4086
  // Game info link
4043
4087
  const infoIcon = makeIcon('info', 22, '#ffffff');
4044
4088
  const infoLabel = textNode(host, host.t('Game info'));
@@ -4082,7 +4126,7 @@ function glassRow(host, children, opts = {}) {
4082
4126
  return row;
4083
4127
  }
4084
4128
  /** A column row: head (label + live % value) over a draggable slider. */
4085
- function sliderRow(host, bodyWidth, key, label) {
4129
+ function sliderRow(host, bodyWidth, key, label, updaters) {
4086
4130
  const row = new FlexBox({
4087
4131
  direction: 'column',
4088
4132
  align: 'stretch',
@@ -4091,14 +4135,19 @@ function sliderRow(host, bodyWidth, key, label) {
4091
4135
  background: { fill: host.tokens.plaqueGlass, radius: 16 },
4092
4136
  });
4093
4137
  const head = new FlexBox({ direction: 'row', align: 'center', justify: 'space-between' });
4094
- const valueText = makeText('100%', { size: 13, weight: '700', color: host.tokens.plaqueLabel });
4138
+ const initial = host.getVolume(key);
4139
+ const valueText = makeText(`${Math.round(initial * 100)}%`, { size: 13, weight: '700', color: host.tokens.plaqueLabel });
4095
4140
  head.add(makeText(label, { size: 14, weight: '600', color: '#ffffff' }));
4096
4141
  head.add(new Spacer(), { grow: 1 });
4097
4142
  head.add(valueText);
4098
- const slider = new Slider(host, 1, (v) => {
4143
+ const slider = new Slider(host, initial, (v) => {
4099
4144
  valueText.text = `${Math.round(v * 100)}%`;
4100
- host.emit('settingChange', { key, value: v });
4145
+ host.setVolume(key, v);
4101
4146
  });
4147
+ updaters[key] = (v) => {
4148
+ valueText.text = `${Math.round(v * 100)}%`;
4149
+ slider.setValue(v);
4150
+ };
4102
4151
  row.add(head);
4103
4152
  row.add(slider);
4104
4153
  return row;
@@ -5882,6 +5931,13 @@ class PixiRenderer {
5882
5931
  // make sure the stage delivers global pointer moves (drag, sliders)
5883
5932
  this.app.stage.eventMode = 'static';
5884
5933
  this.app.renderer.on('resize', this.onResize);
5934
+ // Seed the layout from the CURRENT screen size. The renderer was resized to the container during
5935
+ // boot (ViewportManager.refresh) BEFORE this shell mounted and subscribed above, so that initial
5936
+ // 'resize' event is already gone and won't fire again on a stationary device. Without this seed
5937
+ // the controller's layout stays at its 'wide' DEFAULT — a portrait mobile would show the DESKTOP
5938
+ // bar. (The HTML renderer gets this for free: its ResizeObserver fires immediately on observe.)
5939
+ if (this.screenW > 0)
5940
+ this.host.notifyResize(this.screenW, this.screenH);
5885
5941
  whenFontReady(() => {
5886
5942
  if (!this.destroyed)
5887
5943
  this.renderBar();
@@ -6128,6 +6184,9 @@ class PixiRenderer {
6128
6184
  notifyResize: (w, h) => host.notifyResize(w, h),
6129
6185
  setSound: (on) => host.setSound(on),
6130
6186
  setSoundRefresh: (fn) => host.setSoundRefresh(fn),
6187
+ getVolume: (key) => host.getVolume(key),
6188
+ setVolume: (key, v) => host.setVolume(key, v),
6189
+ setVolumeRefresh: (fn) => host.setVolumeRefresh(fn),
6131
6190
  // — Pixi-specific surface —
6132
6191
  get ticker() { return self.app.ticker; },
6133
6192
  get canvas() { return self.app.canvas; },