@energy8platform/game-engine 0.17.0 → 0.18.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.
Files changed (69) hide show
  1. package/dist/core.cjs.js +62 -1
  2. package/dist/core.cjs.js.map +1 -1
  3. package/dist/core.d.ts +33 -2
  4. package/dist/core.esm.js +63 -3
  5. package/dist/core.esm.js.map +1 -1
  6. package/dist/game-spec.cjs.js +13 -0
  7. package/dist/game-spec.cjs.js.map +1 -0
  8. package/dist/game-spec.d.ts +1 -0
  9. package/dist/game-spec.esm.js +2 -0
  10. package/dist/game-spec.esm.js.map +1 -0
  11. package/dist/host.cjs.js +3346 -0
  12. package/dist/host.cjs.js.map +1 -0
  13. package/dist/host.d.ts +915 -0
  14. package/dist/host.esm.js +3337 -0
  15. package/dist/host.esm.js.map +1 -0
  16. package/dist/index.cjs.js +13 -1
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.ts +6 -1
  19. package/dist/index.esm.js +13 -1
  20. package/dist/index.esm.js.map +1 -1
  21. package/dist/react.cjs.js.map +1 -1
  22. package/dist/react.d.ts +6 -1
  23. package/dist/react.esm.js.map +1 -1
  24. package/dist/shell.cjs.js +19 -0
  25. package/dist/shell.cjs.js.map +1 -0
  26. package/dist/shell.d.ts +1 -0
  27. package/dist/shell.esm.js +2 -0
  28. package/dist/shell.esm.js.map +1 -0
  29. package/dist/slot.cjs.js +998 -0
  30. package/dist/slot.cjs.js.map +1 -0
  31. package/dist/slot.d.ts +333 -0
  32. package/dist/slot.esm.js +985 -0
  33. package/dist/slot.esm.js.map +1 -0
  34. package/package.json +26 -1
  35. package/src/core/GameApplication.ts +16 -1
  36. package/src/core/index.ts +2 -0
  37. package/src/game-spec/index.ts +1 -0
  38. package/src/host/autoplay.ts +78 -0
  39. package/src/host/balanceGate.ts +46 -0
  40. package/src/host/buildConfig.ts +28 -0
  41. package/src/host/createSlotGame.ts +423 -0
  42. package/src/host/fatalError.ts +104 -0
  43. package/src/host/freeSpinsCounter.ts +44 -0
  44. package/src/host/index.ts +18 -0
  45. package/src/host/playError.ts +64 -0
  46. package/src/host/preboot.ts +25 -0
  47. package/src/host/replay.ts +9 -0
  48. package/src/host/runRound.ts +63 -0
  49. package/src/host/sceneController.ts +31 -0
  50. package/src/host/sceneStart.ts +25 -0
  51. package/src/host/shellConfig.ts +379 -0
  52. package/src/host/slotPlay.ts +62 -0
  53. package/src/host/types.ts +71 -0
  54. package/src/scenes/IntroScene.ts +66 -0
  55. package/src/shell/index.ts +20 -0
  56. package/src/slot/anim/CascadeController.ts +102 -0
  57. package/src/slot/anim/ReelSpinController.ts +81 -0
  58. package/src/slot/anim/easing-map.ts +14 -0
  59. package/src/slot/freeSpins/FreeSpinsSession.ts +40 -0
  60. package/src/slot/grid/AnimatedSymbol.ts +68 -0
  61. package/src/slot/grid/ReelGrid.ts +92 -0
  62. package/src/slot/grid/SymbolCell.ts +127 -0
  63. package/src/slot/grid/SymbolView.ts +13 -0
  64. package/src/slot/index.ts +21 -0
  65. package/src/slot/multiplier/MultiplierAccumulator.ts +29 -0
  66. package/src/slot/overlay/BigWinOverlay.ts +89 -0
  67. package/src/slot/overlay/CountUpDisplay.ts +56 -0
  68. package/src/slot/overlay/tiers.ts +29 -0
  69. package/src/types.ts +3 -0
@@ -0,0 +1,13 @@
1
+ import type { Container } from 'pixi.js';
2
+
3
+ /** A symbol's visual. Any Container; optionally implements the lifecycle the cell drives. */
4
+ export interface SymbolView extends Container {
5
+ playIdle?(): void;
6
+ playWin?(): Promise<void>;
7
+ showStatic?(): void;
8
+ /** Resize the symbol to the given cell size in pixels. */
9
+ resize?(size: number): void;
10
+ }
11
+
12
+ /** Game-supplied factory: build the view for a symbol id (sprite / layered sprites / Spine / composite). */
13
+ export type SymbolResolver = (symbolId: string) => SymbolView | null;
@@ -0,0 +1,21 @@
1
+ export type { SymbolView, SymbolResolver } from './grid/SymbolView';
2
+ export { AnimatedSymbol } from './grid/AnimatedSymbol';
3
+ export type { SymbolTextures, AnimatedSymbolConfig } from './grid/AnimatedSymbol';
4
+ export { SymbolCell } from './grid/SymbolCell';
5
+ export type { CellFrameStyle, CellData, CellState, SymbolCellConfig } from './grid/SymbolCell';
6
+ export { ReelGrid } from './grid/ReelGrid';
7
+ export type { DecorationConfig, ReelGridConfig } from './grid/ReelGrid';
8
+ export { CascadeController } from './anim/CascadeController';
9
+ export type { CascadeStepData, CascadeTimings, CascadeAnim } from './anim/CascadeController';
10
+ export { ReelSpinController } from './anim/ReelSpinController';
11
+ export type { ReelSpinData, ReelSpinTimings, ReelStopPlan } from './anim/ReelSpinController';
12
+ export { pickTier, tierIndexAtValue } from './overlay/tiers';
13
+ export type { WinTier } from './overlay/tiers';
14
+ export { valueAt, CountUpDisplay } from './overlay/CountUpDisplay';
15
+ export type { CountUpConfig } from './overlay/CountUpDisplay';
16
+ export { BigWinOverlay } from './overlay/BigWinOverlay';
17
+ export type { BigWinOverlayConfig } from './overlay/BigWinOverlay';
18
+ export { FreeSpinsSession } from './freeSpins/FreeSpinsSession';
19
+ export type { FreeSpinsSessionConfig } from './freeSpins/FreeSpinsSession';
20
+ export { MultiplierAccumulator } from './multiplier/MultiplierAccumulator';
21
+ export type { CarryPolicy } from './multiplier/MultiplierAccumulator';
@@ -0,0 +1,29 @@
1
+ /** The boundary at which a collected multiplier resets. */
2
+ export type CarryPolicy = 'spin' | 'cascade' | 'session';
3
+
4
+ // How long each policy survives: cascade (shortest) < spin < session (longest).
5
+ const RANK: Record<CarryPolicy, number> = { cascade: 0, spin: 1, session: 2 };
6
+
7
+ /**
8
+ * Headless sticky/collector multiplier — the unified abstraction behind
9
+ * kitsunebi / recipe / orb / stage multipliers. reset(boundary) clears the
10
+ * value only when the boundary is at or above the configured policy scope.
11
+ */
12
+ export class MultiplierAccumulator {
13
+ value: number;
14
+ private readonly base: number;
15
+ private readonly policy: CarryPolicy;
16
+
17
+ constructor(cfg: { policy: CarryPolicy; base?: number }) {
18
+ this.policy = cfg.policy;
19
+ this.base = cfg.base ?? 1;
20
+ this.value = this.base;
21
+ }
22
+
23
+ add(delta: number): void { this.value += delta; }
24
+ set(value: number): void { this.value = value; }
25
+
26
+ reset(boundary: CarryPolicy): void {
27
+ if (RANK[boundary] >= RANK[this.policy]) this.value = this.base;
28
+ }
29
+ }
@@ -0,0 +1,89 @@
1
+ import { Container, Graphics, Sprite, Text } from 'pixi.js';
2
+ import { Tween, Easing } from '../../animation';
3
+ import { pickTier, tierIndexAtValue, type WinTier } from './tiers';
4
+ import { CountUpDisplay } from './CountUpDisplay';
5
+
6
+ export interface BigWinOverlayConfig {
7
+ tiers: WinTier[];
8
+ formatMoney: (v: number) => string;
9
+ countUpDuration?: (win: number) => number;
10
+ particleCount?: number;
11
+ width: number;
12
+ height: number;
13
+ }
14
+
15
+ const DEFAULT_DURATION = (win: number) => Math.min(2500, Math.max(800, win * 20));
16
+
17
+ export class BigWinOverlay extends Container {
18
+ readonly __uiComponent = true as const;
19
+
20
+ private _cfg: BigWinOverlayConfig;
21
+ private _dim: Graphics;
22
+ private _banner: Sprite | null = null;
23
+ private _title: Text;
24
+ private _count: CountUpDisplay;
25
+
26
+ constructor(config: BigWinOverlayConfig) {
27
+ super();
28
+ this._cfg = config;
29
+ this.visible = false;
30
+
31
+ this._dim = new Graphics();
32
+ this.addChild(this._dim);
33
+
34
+ this._title = new Text({ text: '', style: { fontFamily: 'sans-serif', fontSize: 72, fill: 0xffffff, fontWeight: '900' } });
35
+ this._title.anchor.set(0.5);
36
+ this.addChild(this._title);
37
+
38
+ this._count = new CountUpDisplay({ format: config.formatMoney });
39
+ this.addChild(this._count);
40
+
41
+ this.resize(config.width, config.height);
42
+ }
43
+
44
+ /** Pure helper (testable): the tier title for a given win/bet, or null below the lowest tier. */
45
+ tierTitleFor(win: number, bet: number): string | null {
46
+ return pickTier(this._cfg.tiers, win, bet)?.title ?? null;
47
+ }
48
+
49
+ resize(width: number, height: number): void {
50
+ this._cfg.width = width; this._cfg.height = height;
51
+ this._dim.clear();
52
+ this._dim.rect(0, 0, width, height).fill({ color: 0x000000, alpha: 0.72 });
53
+ this._title.position.set(width / 2, height * 0.4);
54
+ this._count.position.set(width / 2, height * 0.56);
55
+ }
56
+
57
+ async show(win: number, bet: number, format?: (v: number) => string): Promise<void> {
58
+ const tier = pickTier(this._cfg.tiers, win, bet);
59
+ if (!tier) return;
60
+ if (format) this._count.setFormat(format);
61
+ this.visible = true;
62
+ this.alpha = 0;
63
+ this._title.text = tier.title;
64
+ this._title.style.fill = tier.accentColor;
65
+
66
+ if (this._banner) { this._banner.destroy(); this._banner = null; }
67
+ if (tier.bannerTexture) {
68
+ this._banner = new Sprite(tier.bannerTexture);
69
+ this._banner.anchor.set(0.5);
70
+ this._banner.position.set(this._cfg.width / 2, this._cfg.height * 0.4);
71
+ this.addChildAt(this._banner, 1);
72
+ }
73
+
74
+ await Tween.to(this, { alpha: 1 }, 200, Easing.easeOutQuad);
75
+ const dur = (this._cfg.countUpDuration ?? DEFAULT_DURATION)(win);
76
+ let lastIdx = tierIndexAtValue(this._cfg.tiers, 0, bet);
77
+ await this._count.countTo(win, dur, undefined);
78
+ // tier-promotion title updates as the value climbs (sampled at the end for simplicity)
79
+ const finalIdx = tierIndexAtValue(this._cfg.tiers, win, bet);
80
+ if (finalIdx > lastIdx && this._cfg.tiers[finalIdx]) {
81
+ this._title.text = this._cfg.tiers[finalIdx].title;
82
+ }
83
+ void lastIdx;
84
+ }
85
+
86
+ skip(): void { Tween.killTweensOf(this); this._count.skip(); }
87
+
88
+ hide(): void { this.visible = false; Tween.killTweensOf(this); }
89
+ }
@@ -0,0 +1,56 @@
1
+ import { Container, Text, type TextStyle } from 'pixi.js';
2
+ import { Tween, Easing } from '../../animation';
3
+
4
+ export interface CountUpConfig { format: (v: number) => string; style?: Partial<TextStyle>; }
5
+
6
+ /** PURE eased interpolation 0→target over duration (ms). Clamped to [0, target]. */
7
+ export function valueAt(elapsed: number, target: number, duration: number): number {
8
+ if (duration <= 0 || elapsed >= duration) return target;
9
+ if (elapsed <= 0) return 0;
10
+ const p = elapsed / duration;
11
+ const eased = 1 - Math.pow(1 - p, 3); // easeOutCubic
12
+ return target * eased;
13
+ }
14
+
15
+ export class CountUpDisplay extends Container {
16
+ readonly __uiComponent = true as const;
17
+ private _text: Text;
18
+ private _format: (v: number) => string;
19
+ private _value = 0;
20
+
21
+ constructor(config: CountUpConfig) {
22
+ super();
23
+ this._format = config.format;
24
+ this._text = new Text({
25
+ text: this._format(0),
26
+ style: { fontFamily: 'sans-serif', fontSize: 48, fill: 0xffffff, fontWeight: '800', ...config.style },
27
+ });
28
+ this._text.anchor.set(0.5);
29
+ this.addChild(this._text);
30
+ }
31
+
32
+ get text(): string { return this._text.text; }
33
+
34
+ setValue(v: number): void {
35
+ this._value = v;
36
+ this._text.text = this._format(v);
37
+ }
38
+
39
+ /** Swap the value formatter and immediately re-render the current value. */
40
+ setFormat(format: (v: number) => string): void {
41
+ this._format = format;
42
+ this._text.text = this._format(this._value);
43
+ }
44
+
45
+ /** Animate the value to target over duration; fires onTier on each tier-index increase. */
46
+ async countTo(target: number, duration: number, onTier?: (idx: number) => void): Promise<void> {
47
+ const holder = { v: 0 };
48
+ void onTier; // tier tracking is external — onTier is a forward-compat hook
49
+ await Tween.to(holder, { v: target }, duration, Easing.easeOutCubic, () => {
50
+ this.setValue(holder.v);
51
+ });
52
+ this.setValue(target);
53
+ }
54
+
55
+ skip(): void { Tween.killTweensOf(this); }
56
+ }
@@ -0,0 +1,29 @@
1
+ import type { Texture } from 'pixi.js';
2
+
3
+ export interface WinTier {
4
+ id: string;
5
+ minMultiplier: number;
6
+ title: string;
7
+ accentColor: number;
8
+ bannerTexture?: Texture;
9
+ }
10
+
11
+ /** Highest tier whose minMultiplier <= win/bet, or null if below the lowest. */
12
+ export function pickTier(tiers: WinTier[], win: number, bet: number): WinTier | null {
13
+ if (bet <= 0) return null;
14
+ const mult = win / bet;
15
+ let chosen: WinTier | null = null;
16
+ for (const t of tiers) {
17
+ if (mult >= t.minMultiplier && (!chosen || t.minMultiplier >= chosen.minMultiplier)) chosen = t;
18
+ }
19
+ return chosen;
20
+ }
21
+
22
+ /** Index into `tiers` for the running value (or -1 below the lowest tier). */
23
+ export function tierIndexAtValue(tiers: WinTier[], runningValue: number, bet: number): number {
24
+ if (bet <= 0) return -1;
25
+ const mult = runningValue / bet;
26
+ let idx = -1;
27
+ for (let i = 0; i < tiers.length; i++) if (mult >= tiers[i].minMultiplier) idx = i;
28
+ return idx;
29
+ }
package/src/types.ts CHANGED
@@ -111,6 +111,9 @@ export interface GameApplicationConfig {
111
111
 
112
112
  /** Enable debug overlay (FPS, draw calls) */
113
113
  debug?: boolean;
114
+
115
+ /** When set, GameApplication mounts the branded game shell after the SDK handshake. */
116
+ shell?: import('@energy8platform/platform-core/shell').ShellConfig | false;
114
117
  }
115
118
 
116
119
  // ─── Scene Types ───────────────────────────────────────────