@energy8platform/platform-core 0.20.0 → 0.22.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/README.md CHANGED
@@ -21,6 +21,7 @@ If you want the full PixiJS engine on top of this, install [`@energy8platform/ga
21
21
  - [Vite Plugins](#vite-plugins)
22
22
  - [Asset Manifest type](#asset-manifest-type)
23
23
  - [Pairing with another renderer](#pairing-with-another-renderer)
24
+ - [Branded Game Shell](#branded-game-shell)
24
25
  - [Sub-path exports](#sub-path-exports)
25
26
  - [License](#license)
26
27
 
@@ -607,6 +608,200 @@ Nothing in this code is Pixi-specific. The same pattern fits Three.js, Babylon,
607
608
 
608
609
  ---
609
610
 
611
+ ## Branded Game Shell
612
+
613
+ `@energy8platform/platform-core/shell` is a **vanilla-DOM UI overlay** you layer over the game
614
+ canvas — no Pixi, no React, no framework. It owns the control bar (3 modes: base / freeSpins /
615
+ replay), the menu, settings, the game-info panel, and a buy-bonus selection overlay, plus generic
616
+ modals and a replay summary. Branded Energy8 chrome, fully renderer-agnostic — pair it with Pixi,
617
+ Phaser, Three.js, or a custom engine.
618
+
619
+ > Also re-exported from `@energy8platform/game-engine/shell` — same module, no extra install for
620
+ > Pixi consumers.
621
+
622
+ ### Mental model
623
+
624
+ The shell is **fully driven by the game** (single source of truth). It does **not** subscribe to
625
+ the SDK/session and holds no game logic. You:
626
+
627
+ 1. **Feed state in** — once via the config object, then over time via `set*` methods.
628
+ 2. **React to player intent out** — subscribe to typed events (`spin`, `betChange`, …) and run
629
+ your game logic, then push the resulting state back via setters.
630
+
631
+ This keeps replay and mid-spin restore deterministic: the shell never decides anything, it only
632
+ renders what you tell it and reports what the player tapped.
633
+
634
+ ### Quick start
635
+
636
+ ```typescript
637
+ import { createGameShell, removeGameShell } from '@energy8platform/platform-core/shell';
638
+
639
+ const shell = createGameShell({
640
+ mount: document.getElementById('game')!, // shell appends its DOM here (position it relative)
641
+ language: 'en',
642
+ currency: { symbol: '€', position: 'left' },
643
+ availableBets: [0.2, 0.5, 1, 2, 5],
644
+ defaultBet: 1,
645
+ currentBet: null, // null → start at defaultBet; or restore a saved bet
646
+ balance: 1000,
647
+ win: 0,
648
+ mode: 'base',
649
+ gameInfo: { sections: [{ type: 'controls' }] }, // see "Game info" below
650
+ features: {
651
+ turbo: 3, // 0 = no turbo button, 1–3 = number of turbo levels
652
+ spacebar: true, // default true; set false to disable the Spacebar → spin shortcut
653
+ autoplay: {}, // null / omitted = off; {} = on; { maxCount: 100 } caps the picker
654
+ buyBonus: [
655
+ { id: 'fs', type: 'bonus', title: 'Buy Free Spins', description: '10 free spins',
656
+ priceMultiplier: 100, volatility: 5 },
657
+ ],
658
+ },
659
+ });
660
+
661
+ // ── player intent (shell → game) ──
662
+ shell.on('spin', () => runSpin(shell.state.bet));
663
+ shell.on('betChange', (bet) => { myState.bet = bet; });
664
+ shell.on('buyBonusSelect', ({ id }) => buyFeature(id));
665
+
666
+ // ── game state (game → shell) ──
667
+ shell.setBusy(true); // disable controls during an active spin
668
+ shell.setBalance(980);
669
+ shell.setWin(20); // both readouts count up automatically
670
+ shell.setBusy(false);
671
+
672
+ // teardown (single shell per page; fades out, resolves when removed)
673
+ await removeGameShell();
674
+ ```
675
+
676
+ `createGameShell` is a **singleton** — calling it twice returns the existing shell. Use
677
+ `removeGameShell()` to dispose before creating another.
678
+
679
+ ### Config reference (`ShellConfig`)
680
+
681
+ | Field | Type | Notes |
682
+ | --- | --- | --- |
683
+ | `mount` | `HTMLElement` | Container the shell DOM is appended into. Give it `position: relative`. |
684
+ | `theme` | `ThemeConfig?` | `{ scheme?: 'dark' \| 'light', accent?, buyBonusColor? }`. Defaults to dark. |
685
+ | `language` | `string` | Currently `'en'` is the source language. |
686
+ | `isSocial` | `boolean?` | Swap built-in text to social-casino vocabulary (bet → play, win → …). Game-supplied strings are untouched. |
687
+ | `currency` | `CurrencyConfig` | `{ symbol, position: 'left'\|'right', decimals?, minDecimals?, separator? }`. |
688
+ | `availableBets` | `number[]` | Bet ladder shown in the bet picker. |
689
+ | `defaultBet` / `currentBet` | `number` / `number \| null` | `currentBet` restores a saved bet; `null` falls back to `defaultBet`. |
690
+ | `balance` / `win` | `number` | Initial readouts. |
691
+ | `mode` | `'base' \| 'freeSpins' \| 'replay'` | Drives which bottom-bar variant renders. |
692
+ | `gameInfo` | `GameInfoContent` | Sections for the game-info overlay (see below). |
693
+ | `features` | `ShellFeatures` | `{ turbo: 0–3, spacebar?, autoplay, buyBonus }`. `spacebar?: boolean` (default `true`) — `false` disables the Spacebar → spin shortcut. `autoplay: AutoplayConfig \| null` — `null`/omitted disables it; `{}` enables it; `{ maxCount }` caps the picker (drops ∞). `buyBonus: BonusOption[] \| false`. |
694
+
695
+ ### Events (`shell.on(name, handler)`)
696
+
697
+ | Event | Payload | When |
698
+ | --- | --- | --- |
699
+ | `spin` | — | Spin disc tapped (or Spacebar in base mode). |
700
+ | `betChange` | `number` | Player confirmed a new bet. |
701
+ | `autoplayStart` / `autoplayStop` | `{ active, remaining }` / — | Autoplay picker confirmed / stopped. |
702
+ | `turboChange` | `number` | Turbo level cycled. |
703
+ | `buyBonusSelect` | `{ id }` | A `type: 'bonus'` card was bought. |
704
+ | `featureActivate` / `featureDeactivate` | `{ id }` | A `type: 'feature'` option (e.g. Ante) toggled. |
705
+ | `menuOpen` / `settingsOpen` / `infoOpen` | — | Overlay opened. |
706
+ | `settingChange` | `{ key, value }` | Settings control changed. Keys: `sound` (bool), `master` / `music` / `sfx` (0–100). |
707
+
708
+ ### State setters (`game → shell`)
709
+
710
+ Each setter updates `shell.state` and re-renders. `setBalance` / `setWin` animate a count-up from
711
+ the previous value.
712
+
713
+ ```typescript
714
+ shell.setBalance(n); shell.setWin(n); shell.setBet(n);
715
+ shell.setBusy(true); // disables controls mid-spin
716
+ shell.setMode('freeSpins');
717
+ shell.setFreeSpins({ current: 1, total: 10, totalWin: 0, lastWin: 0 }); // freeSpins bar readout
718
+ shell.setAutoplay({ active: true, remaining: 25 });
719
+ shell.setTurbo(2);
720
+ shell.setBuyBonusEnabled(false); // grey out BUY BONUS (e.g. insufficient balance)
721
+ shell.setTheme({ scheme: 'light' }); // recolour at runtime
722
+ shell.setSocial(true); // swap vocabulary at runtime (reopen overlays to refresh them)
723
+ ```
724
+
725
+ Read current state any time via `shell.state` (`ShellState`: `mode`, `balance`, `win`, `bet`,
726
+ `busy`, `autoplay`, `turbo`, `freeSpins`, `activeFeature`, …).
727
+
728
+ ### Buy bonus & features
729
+
730
+ `features.buyBonus` is an array of cards. `type: 'bonus'` buys into a round (emits
731
+ `buyBonusSelect`); `type: 'feature'` toggles a base-game modifier like Ante. For features, drive
732
+ the bar readout with:
733
+
734
+ ```typescript
735
+ shell.activateFeature(option); // bar shows the effective bet, BUY BONUS → DISABLE
736
+ shell.deactivateFeature(); // revert
737
+ ```
738
+
739
+ Each card price renders as `priceMultiplier × current bet` in the shell currency.
740
+
741
+ ### Game info (`gameInfo.sections`)
742
+
743
+ The game-info overlay is composed from typed sections — declare what your game has and the shell
744
+ draws the rest:
745
+
746
+ - `{ type: 'modes', modes: GameMode[] }` — comparison table (title / price / rtp / maxWin).
747
+ - `{ type: 'controls' }` — auto-generated control legend.
748
+ - `{ type: 'paytable', rows: PaytableRow[] }` — symbol → win tiers (`"<count> x<multiplier>"`).
749
+ - `{ type: 'wins', kind, grid, … }` — auto-drawn win illustration. `kind` is `'classic'` (paylines),
750
+ `'cluster'`, `'anywhere'`, or `'ways'`.
751
+ - `{ type: 'custom', title, html | node }` — your own rules markup.
752
+
753
+ ```typescript
754
+ gameInfo: {
755
+ sections: [
756
+ { type: 'modes', modes: [{ title: 'Base game', price: '1× bet', rtp: 96.5, maxWin: '5,000×' }] },
757
+ { type: 'controls' },
758
+ { type: 'paytable', rows: [
759
+ { symbol: { text: 'Wild' }, wins: [{ count: '5', multiplier: 250 }, { count: '3', multiplier: 50 }] },
760
+ ] },
761
+ { type: 'wins', kind: 'classic', grid: { cols: 5, rows: 3 },
762
+ lines: [[1,1,1,1,1], [0,0,0,0,0], [2,2,2,2,2]] },
763
+ { type: 'custom', title: 'Rules', html: '<p>Match left to right on adjacent reels.</p>' },
764
+ ],
765
+ }
766
+ ```
767
+
768
+ ### Opening overlays & modals programmatically
769
+
770
+ ```typescript
771
+ shell.openSettings(); shell.openInfo(); shell.openBuyBonus();
772
+ shell.openBetPicker(); shell.openAutoplayPicker();
773
+
774
+ // generic card modal
775
+ shell.openModal({
776
+ availableClose: true,
777
+ title: 'Connection lost',
778
+ body: 'Reconnecting…',
779
+ actions: [{ title: 'Retry', color: '#e11', on: () => reconnect() }],
780
+ });
781
+
782
+ // non-dismissable replay summary (START REPLAY → onReplay → reopen)
783
+ shell.openReplay({ bonusId: 'fs', bet: shell.state.bet, payoutMultiplier: 87.5,
784
+ onReplay: () => playRecordedRound() });
785
+ ```
786
+
787
+ ### Layout & visual system
788
+
789
+ Transparent neutral chrome that doesn't compete with the game — brand colour appears only on the
790
+ BUY BONUS control and a duotone icon set. The bottom bar **adapts by viewport** automatically (a
791
+ `ResizeObserver` on the mount): landscape → one row scaled to fit, portrait → stacked mobile
792
+ layout; Settings / Game info / Buy bonus open as full-screen overlays. Motion is minimal (press
793
+ feedback, money count-up, overlay fades) and respects `prefers-reduced-motion`. Spacebar triggers
794
+ a spin in base mode (ignored while busy, in autoplay, when a modal/input is focused, or when
795
+ `features.spacebar` is `false`).
796
+
797
+ ### Live demo
798
+
799
+ [`examples/shell-demo`](../../examples/shell-demo) is a full reference integration: every config
800
+ section, all three bar modes, theme/social toggles, viewport presets, and event wiring. QA params:
801
+ `?screen=<id>&kiosk=1&open=settings|info|buybonus`.
802
+
803
+ ---
804
+
610
805
  ## Sub-path exports
611
806
 
612
807
  | Path | What's there |
@@ -617,6 +812,7 @@ Nothing in this code is Pixi-specific. The same pattern fits Three.js, Babylon,
617
812
  | `@energy8platform/platform-core/dev-bridge` | `DevBridge`, `DevBridgeConfig`, `ReplayConfig`, `ReplayLaunch` |
618
813
  | `@energy8platform/platform-core/vite` | `devBridgePlugin`, `luaPlugin` |
619
814
  | `@energy8platform/platform-core/loading` | `createCSSPreloader`, `setCSSPreloaderProgress`, `waitCSSPreloaderTap`, `removeCSSPreloader`, `buildLogoSVG`, `LOADER_BAR_MAX_WIDTH` |
815
+ | `@energy8platform/platform-core/shell` | `createGameShell`, `removeGameShell` — branded renderer-agnostic DOM game shell (control bar, menu, settings, game info, buy bonus) |
620
816
 
621
817
  The sub-paths exist for tree-shaking — pulling only `/lua` doesn't drag in DevBridge or vite types. The main entry is convenient for app-level code where size hardly matters.
622
818