@energy8platform/platform-core 0.20.0 → 0.21.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,198 @@ 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
+ autoplay: true,
653
+ buyBonus: [
654
+ { id: 'fs', type: 'bonus', title: 'Buy Free Spins', description: '10 free spins',
655
+ priceMultiplier: 100, volatility: 5 },
656
+ ],
657
+ },
658
+ });
659
+
660
+ // ── player intent (shell → game) ──
661
+ shell.on('spin', () => runSpin(shell.state.bet));
662
+ shell.on('betChange', (bet) => { myState.bet = bet; });
663
+ shell.on('buyBonusSelect', ({ id }) => buyFeature(id));
664
+
665
+ // ── game state (game → shell) ──
666
+ shell.setBusy(true); // disable controls during an active spin
667
+ shell.setBalance(980);
668
+ shell.setWin(20); // both readouts count up automatically
669
+ shell.setBusy(false);
670
+
671
+ // teardown (single shell per page; fades out, resolves when removed)
672
+ await removeGameShell();
673
+ ```
674
+
675
+ `createGameShell` is a **singleton** — calling it twice returns the existing shell. Use
676
+ `removeGameShell()` to dispose before creating another.
677
+
678
+ ### Config reference (`ShellConfig`)
679
+
680
+ | Field | Type | Notes |
681
+ | --- | --- | --- |
682
+ | `mount` | `HTMLElement` | Container the shell DOM is appended into. Give it `position: relative`. |
683
+ | `theme` | `ThemeConfig?` | `{ scheme?: 'dark' \| 'light', accent?, buyBonusColor? }`. Defaults to dark. |
684
+ | `language` | `string` | Currently `'en'` is the source language. |
685
+ | `isSocial` | `boolean?` | Swap built-in text to social-casino vocabulary (bet → play, win → …). Game-supplied strings are untouched. |
686
+ | `currency` | `CurrencyConfig` | `{ symbol, position: 'left'\|'right', decimals?, minDecimals?, separator? }`. |
687
+ | `availableBets` | `number[]` | Bet ladder shown in the bet picker. |
688
+ | `defaultBet` / `currentBet` | `number` / `number \| null` | `currentBet` restores a saved bet; `null` falls back to `defaultBet`. |
689
+ | `balance` / `win` | `number` | Initial readouts. |
690
+ | `mode` | `'base' \| 'freeSpins' \| 'replay'` | Drives which bottom-bar variant renders. |
691
+ | `gameInfo` | `GameInfoContent` | Sections for the game-info overlay (see below). |
692
+ | `features` | `ShellFeatures` | `{ turbo: 0–3, autoplay, buyBonus: BonusOption[] \| false }`. |
693
+
694
+ ### Events (`shell.on(name, handler)`)
695
+
696
+ | Event | Payload | When |
697
+ | --- | --- | --- |
698
+ | `spin` | — | Spin disc tapped (or Spacebar in base mode). |
699
+ | `betChange` | `number` | Player confirmed a new bet. |
700
+ | `autoplayStart` / `autoplayStop` | `{ active, remaining }` / — | Autoplay picker confirmed / stopped. |
701
+ | `turboChange` | `number` | Turbo level cycled. |
702
+ | `buyBonusSelect` | `{ id }` | A `type: 'bonus'` card was bought. |
703
+ | `featureActivate` / `featureDeactivate` | `{ id }` | A `type: 'feature'` option (e.g. Ante) toggled. |
704
+ | `menuOpen` / `settingsOpen` / `infoOpen` | — | Overlay opened. |
705
+ | `settingChange` | `{ key, value }` | Settings control changed. Keys: `sound` (bool), `master` / `music` / `sfx` (0–100). |
706
+
707
+ ### State setters (`game → shell`)
708
+
709
+ Each setter updates `shell.state` and re-renders. `setBalance` / `setWin` animate a count-up from
710
+ the previous value.
711
+
712
+ ```typescript
713
+ shell.setBalance(n); shell.setWin(n); shell.setBet(n);
714
+ shell.setBusy(true); // disables controls mid-spin
715
+ shell.setMode('freeSpins');
716
+ shell.setFreeSpins({ current: 1, total: 10, totalWin: 0, lastWin: 0 }); // freeSpins bar readout
717
+ shell.setAutoplay({ active: true, remaining: 25 });
718
+ shell.setTurbo(2);
719
+ shell.setBuyBonusEnabled(false); // grey out BUY BONUS (e.g. insufficient balance)
720
+ shell.setTheme({ scheme: 'light' }); // recolour at runtime
721
+ shell.setSocial(true); // swap vocabulary at runtime (reopen overlays to refresh them)
722
+ ```
723
+
724
+ Read current state any time via `shell.state` (`ShellState`: `mode`, `balance`, `win`, `bet`,
725
+ `busy`, `autoplay`, `turbo`, `freeSpins`, `activeFeature`, …).
726
+
727
+ ### Buy bonus & features
728
+
729
+ `features.buyBonus` is an array of cards. `type: 'bonus'` buys into a round (emits
730
+ `buyBonusSelect`); `type: 'feature'` toggles a base-game modifier like Ante. For features, drive
731
+ the bar readout with:
732
+
733
+ ```typescript
734
+ shell.activateFeature(option); // bar shows the effective bet, BUY BONUS → DISABLE
735
+ shell.deactivateFeature(); // revert
736
+ ```
737
+
738
+ Each card price renders as `priceMultiplier × current bet` in the shell currency.
739
+
740
+ ### Game info (`gameInfo.sections`)
741
+
742
+ The game-info overlay is composed from typed sections — declare what your game has and the shell
743
+ draws the rest:
744
+
745
+ - `{ type: 'modes', modes: GameMode[] }` — comparison table (title / price / rtp / maxWin).
746
+ - `{ type: 'controls' }` — auto-generated control legend.
747
+ - `{ type: 'paytable', rows: PaytableRow[] }` — symbol → win tiers (`"<count> x<multiplier>"`).
748
+ - `{ type: 'wins', kind, grid, … }` — auto-drawn win illustration. `kind` is `'classic'` (paylines),
749
+ `'cluster'`, `'anywhere'`, or `'ways'`.
750
+ - `{ type: 'custom', title, html | node }` — your own rules markup.
751
+
752
+ ```typescript
753
+ gameInfo: {
754
+ sections: [
755
+ { type: 'modes', modes: [{ title: 'Base game', price: '1× bet', rtp: 96.5, maxWin: '5,000×' }] },
756
+ { type: 'controls' },
757
+ { type: 'paytable', rows: [
758
+ { symbol: { text: 'Wild' }, wins: [{ count: '5', multiplier: 250 }, { count: '3', multiplier: 50 }] },
759
+ ] },
760
+ { type: 'wins', kind: 'classic', grid: { cols: 5, rows: 3 },
761
+ lines: [[1,1,1,1,1], [0,0,0,0,0], [2,2,2,2,2]] },
762
+ { type: 'custom', title: 'Rules', html: '<p>Match left to right on adjacent reels.</p>' },
763
+ ],
764
+ }
765
+ ```
766
+
767
+ ### Opening overlays & modals programmatically
768
+
769
+ ```typescript
770
+ shell.openSettings(); shell.openInfo(); shell.openBuyBonus();
771
+ shell.openBetPicker(); shell.openAutoplayPicker();
772
+
773
+ // generic card modal
774
+ shell.openModal({
775
+ availableClose: true,
776
+ title: 'Connection lost',
777
+ body: 'Reconnecting…',
778
+ actions: [{ title: 'Retry', color: '#e11', on: () => reconnect() }],
779
+ });
780
+
781
+ // non-dismissable replay summary (START REPLAY → onReplay → reopen)
782
+ shell.openReplay({ bonusId: 'fs', bet: shell.state.bet, payoutMultiplier: 87.5,
783
+ onReplay: () => playRecordedRound() });
784
+ ```
785
+
786
+ ### Layout & visual system
787
+
788
+ Transparent neutral chrome that doesn't compete with the game — brand colour appears only on the
789
+ BUY BONUS control and a duotone icon set. The bottom bar **adapts by viewport** automatically (a
790
+ `ResizeObserver` on the mount): landscape → one row scaled to fit, portrait → stacked mobile
791
+ layout; Settings / Game info / Buy bonus open as full-screen overlays. Motion is minimal (press
792
+ feedback, money count-up, overlay fades) and respects `prefers-reduced-motion`. Spacebar triggers
793
+ a spin in base mode (ignored while busy, in autoplay, or when a modal/input is focused).
794
+
795
+ ### Live demo
796
+
797
+ [`examples/shell-demo`](../../examples/shell-demo) is a full reference integration: every config
798
+ section, all three bar modes, theme/social toggles, viewport presets, and event wiring. QA params:
799
+ `?screen=<id>&kiosk=1&open=settings|info|buybonus`.
800
+
801
+ ---
802
+
610
803
  ## Sub-path exports
611
804
 
612
805
  | Path | What's there |
@@ -617,6 +810,7 @@ Nothing in this code is Pixi-specific. The same pattern fits Three.js, Babylon,
617
810
  | `@energy8platform/platform-core/dev-bridge` | `DevBridge`, `DevBridgeConfig`, `ReplayConfig`, `ReplayLaunch` |
618
811
  | `@energy8platform/platform-core/vite` | `devBridgePlugin`, `luaPlugin` |
619
812
  | `@energy8platform/platform-core/loading` | `createCSSPreloader`, `setCSSPreloaderProgress`, `waitCSSPreloaderTap`, `removeCSSPreloader`, `buildLogoSVG`, `LOADER_BAR_MAX_WIDTH` |
813
+ | `@energy8platform/platform-core/shell` | `createGameShell`, `removeGameShell` — branded renderer-agnostic DOM game shell (control bar, menu, settings, game info, buy bonus) |
620
814
 
621
815
  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
816